function RegionMoveRect takes region reg, rect rec, real newX, real newY returns nothing
call RegionClearRect(reg, rec)
call MoveRectTo(rec, newX, newY)
call RegionAddRect(reg, rec)
endfunction
Name | Type | is_array | initial_value |
//! novjass
As much as I would like to make my own libraries for this contest, I would have to stick with the already approved
ones...
Flux - DummyRecycler, DamagePackage
Bribe - Table, TimerUtilsEx
Magtheridon96, Vexorian - TimerUtilsEx
AGD - Global Alloc
//! endnovjass
library Alloc /* v1.1.0 https://www.hiveworkshop.com/threads/324937/
*/uses /*
*/Table /* https://www.hiveworkshop.com/threads/188084/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
/*
Written by AGD, based on MyPad's allocation algorithm
A allocator module using a single global indexed stack. Allocated values are
within the JASS_MAX_ARRAY_SIZE. No more need to worry about code bloats behind
the module implementation as it generates the least code possible (6 lines of
code in non-DEBUG_MODE), nor does it use an initialization function. This system
also only uses ONE variable (for the whole map) for the hashtable.
*/
|-----|
| API |
|-----|
/*
*/module GlobalAlloc/*
- Uses a single stack globally
*/module Alloc/*
- Uses a unique stack per struct
*/debug readonly boolean allocated/* Is node allocated?
*/static method allocate takes nothing returns thistype/*
*/method deallocate takes nothing returns nothing/*
*///! endnovjass
/*===========================================================================*/
globals
private key stack
endglobals
static if DEBUG_MODE then
private function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
else
if condition then
call BJDebugMsg("[Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
endif
endif
endfunction
public function IsAllocated takes integer typeId, integer node returns boolean
return node > 0 and Table(stack)[typeId*JASS_MAX_ARRAY_SIZE + node] == 0
endfunction
endif
public function Allocate takes integer typeId returns integer
local integer offset = typeId*JASS_MAX_ARRAY_SIZE
local integer node = Table(stack)[offset]
local integer stackNext = Table(stack)[offset + node]
debug call AssertError(typeId < 0, "allocate()", Table(stack).string[-typeId], 0, "Invalid struct ID (" + I2S(typeId) + ")")
if stackNext == 0 then
debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", Table(stack).string[-typeId], node, "Overflow")
set node = node + 1
set Table(stack)[offset] = node
else
set Table(stack)[offset] = stackNext
set Table(stack)[offset + node] = 0
endif
return node
endfunction
public function Deallocate takes integer typeId, integer node returns nothing
local integer offset = typeId*JASS_MAX_ARRAY_SIZE
debug call AssertError(node == 0, "deallocate()", Table(stack).string[-typeId], 0, "Null node")
debug call AssertError(Table(stack)[offset + node] > 0, "deallocate()", Table(stack).string[-typeId], node, "Double-free")
set Table(stack)[offset + node] = Table(stack)[offset]
set Table(stack)[offset] = node
endfunction
module Alloc
debug method operator allocated takes nothing returns boolean
debug return IsAllocated(thistype.typeid, this)
debug endmethod
static method allocate takes nothing returns thistype
return Allocate(thistype.typeid)
endmethod
method deallocate takes nothing returns nothing
call Deallocate(thistype.typeid, this)
endmethod
debug private static method onInit takes nothing returns nothing
debug set Table(stack).string[-thistype.typeid] = "thistype"
debug endmethod
endmodule
module GlobalAlloc
debug method operator allocated takes nothing returns boolean
debug return IsAllocated(0, this)
debug endmethod
static method allocate takes nothing returns thistype
debug call AssertError(Table(stack)[0] == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", "thistype", JASS_MAX_ARRAY_SIZE - 1, "Overflow")
return Allocate(0)
endmethod
method deallocate takes nothing returns nothing
debug call AssertError(this == 0, "deallocate()", "thistype", 0, "Null node")
debug call AssertError(Table(stack)[this] > 0, "deallocate()", "thistype", this, "Double-free")
call Deallocate(0, this)
endmethod
endmodule
endlibrary
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
function GetHash takes nothing returns hashtable
return ht
endfunction
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
/*****************************************************************************
*
* List<T> v2.1.2.3
* by Bannar
*
* Doubly-linked list.
*
******************************************************************************
*
* Requirements:
*
* Table by Bribe
* hiveworkshop.com/threads/snippet-new-table.188084/
*
* Alloc - choose whatever you like
* e.g.: by Sevion hiveworkshop.com/threads/snippet-alloc.192348/
*
******************************************************************************
*
* Implementation:
*
* macro DEFINE_LIST takes ACCESS, NAME, TYPE
*
* macro DEFINE_STRUCT_LIST takes ACCESS, NAME, TYPE
*
* ACCESS - encapsulation, choose restriction access
* NAME - name of list type
* TYPE - type of values stored
*
* Implementation notes:
*
* - DEFINE_STRUCT_LIST macro purpose is to provide natural typecasting syntax for struct types.
* - <NAME>Item structs inline directly into hashtable operations thus generate basically no code.
* - Lists defined with DEFINE_STRUCT_LIST are inlined nicely into single create method and single integer array.
*
******************************************************************************
*
* struct API:
*
* struct <NAME>Item:
*
* | <TYPE> data
* | <NAME>Item next
* | <NAME>Item prev
*
*
* General:
*
* | static method create takes nothing returns thistype
* | Default ctor.
* |
* | static method operator [] takes thistype other returns thistype
* | Copy ctor.
* |
* | method destroy takes nothing returns nothing
* | Default dctor.
* |
* | method empty takes nothing returns boolean
* | Checks whether the list is empty.
* |
* | method size takes nothing returns integer
* | Returns size of a list.
*
*
* Access:
*
* | readonly <NAME>Item first
* | readonly <NAME>Item last
* |
* | method front takes nothing returns $TYPE$
* | Retrieves first element.
* |
* | method back takes nothing returns $TYPE$
* | Retrieves last element.
*
*
* Modifiers:
*
* | method clear takes nothing returns nothing
* | Flushes list and recycles its nodes.
* |
* | method push takes $TYPE$ value returns thistype
* | Adds elements to the end.
* |
* | method unshift takes $TYPE$ value returns thistype
* | Adds elements to the front.
* |
* | method pop takes nothing returns thistype
* | Removes the last element.
* |
* | method shift takes nothing returns thistype
* | Removes the first element.
* |
* | method find takes $TYPE$ value returns $NAME$Item
* | Returns the first node which data equals value.
* |
* | method erase takes $NAME$Item node returns boolean
* | Removes node from the list, returns true on success.
* |
* | method removeElem takes $TYPE$ value returns thistype
* | Removes first element that equals value from the list.
*
*
*****************************************************************************/
library ListT requires Table, Alloc
//! runtextmacro DEFINE_LIST("", "IntegerList", "integer")
// Run here any global list types you want to be defined.
//! textmacro_once DEFINE_LIST takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$Item extends array
// No default ctor and dctor due to limited array size
method operator data takes nothing returns $TYPE$
return Table(this).$TYPE$[-1] // hashtable[ node, -1 ] = data
endmethod
method operator data= takes $TYPE$ value returns nothing
set Table(this).$TYPE$[-1] = value
endmethod
method operator next takes nothing returns thistype
return Table(this)[-2] // hashtable[ node, -2 ] = next
endmethod
method operator next= takes thistype value returns nothing
set Table(this)[-2] = value
endmethod
method operator prev takes nothing returns thistype
return Table(this)[-3] // hashtable[ node, -3 ] = prev
endmethod
method operator prev= takes thistype value returns nothing
set Table(this)[-3] = value
endmethod
endstruct
$ACCESS$ struct $NAME$ extends array
readonly $NAME$Item first
readonly $NAME$Item last
private integer count
implement Alloc
private static method setNodeOwner takes $NAME$Item node, $NAME$ owner returns nothing
set Table(node)[-4] = owner
endmethod
private static method getNodeOwner takes $NAME$Item node returns thistype
return Table(node)[-4]
endmethod
private method createNode takes $TYPE$ value returns $NAME$Item
local $NAME$Item node = Table.create()
set node.data = value
call setNodeOwner(node, this) // ownership
return node
endmethod
private method deleteNode takes $NAME$Item node returns nothing
call Table(node).destroy() // also removes ownership
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set count = 0
return this
endmethod
method clear takes nothing returns nothing
local $NAME$Item node = first
local $NAME$Item temp
loop // recycle all Table indexes
exitwhen 0 == node
set temp = node.next
call deleteNode(node)
set node = temp
endloop
set first = 0
set last = 0
set count = 0
endmethod
method destroy takes nothing returns nothing
call clear()
call deallocate()
endmethod
method front takes nothing returns $TYPE$
return first.data
endmethod
method back takes nothing returns $TYPE$
return last.data
endmethod
method empty takes nothing returns boolean
return count == 0
endmethod
method size takes nothing returns integer
return count
endmethod
method push takes $TYPE$ value returns thistype
local $NAME$Item node = createNode(value)
if not empty() then
set last.next = node
set node.prev = last
else
set first = node
set node.prev = 0
endif
set last = node
set node.next = 0
set count = count + 1
return this
endmethod
method unshift takes $TYPE$ value returns thistype
local $NAME$Item node = createNode(value)
if not empty() then
set first.prev = node
set node.next = first
else
set last = node
set node.next = 0
endif
set first = node
set node.prev = 0
set count = count + 1
return this
endmethod
method pop takes nothing returns thistype
local $NAME$Item node
if not empty() then
set node = last
set last = last.prev
if last == 0 then
set first = 0
else
set last.next = 0
endif
call deleteNode(node)
set count = count - 1
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::pop failed for instance "+I2S(this)+". List is empty.")
endif
return this
endmethod
method shift takes nothing returns thistype
local $NAME$Item node
if not empty() then
set node = first
set first = first.next
if first == 0 then
set last = 0
else
set first.prev = 0
endif
call deleteNode(node)
set count = count - 1
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::shift failed for instance "+I2S(this)+". List is empty.")
endif
return this
endmethod
static method operator [] takes thistype other returns thistype
local thistype instance = create()
local $NAME$Item node = other.first
loop
exitwhen node == 0
call instance.push(node.data)
set node = node.next
endloop
return instance
endmethod
method find takes $TYPE$ value returns $NAME$Item
local $NAME$Item node = first
loop
exitwhen node == 0 or node.data == value
set node = node.next
endloop
return node
endmethod
method erase takes $NAME$Item node returns boolean
if getNodeOwner(node) == this then // match ownership
if node == first then
call shift()
elseif node == last then
call pop()
else
set node.prev.next = node.next
set node.next.prev = node.prev
call deleteNode(node)
set count = count - 1
endif
return true
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::erase failed for instance "+I2S(this)+". Attempted to remove invalid node "+I2S(node)+".")
endif
return false
endmethod
method remove takes $NAME$Item node returns boolean
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Method $NAME$::remove is obsolete, use $NAME$::erase instead.")
return erase(node)
endmethod
method removeElem takes $TYPE$ value returns thistype
local $NAME$Item node = find(value)
if node != 0 then
call erase(node)
endif
return this
endmethod
endstruct
//! endtextmacro
//! textmacro_once DEFINE_STRUCT_LIST takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$Item extends array
// Cannot inherit methods via delegate due to limited array size
method operator data takes nothing returns $TYPE$
return IntegerListItem(this).data
endmethod
method operator data= takes $TYPE$ value returns nothing
set IntegerListItem(this).data = value
endmethod
method operator next takes nothing returns thistype
return IntegerListItem(this).next
endmethod
method operator next= takes thistype value returns nothing
set IntegerListItem(this).next = value
endmethod
method operator prev takes nothing returns thistype
return IntegerListItem(this).prev
endmethod
method operator prev= takes thistype value returns nothing
set IntegerListItem(this).prev = value
endmethod
endstruct
$ACCESS$ struct $NAME$ extends array
private delegate IntegerList parent
static method create takes nothing returns thistype
local thistype this = IntegerList.create()
set parent = this
return this
endmethod
method front takes nothing returns $TYPE$
return parent.front()
endmethod
method back takes nothing returns $TYPE$
return parent.back()
endmethod
endstruct
//! endtextmacro
endlibrary
library DummyRecycler /*
// DummyRecycler v1.25
// by Flux
//
// A system that recycles dummy units while considering their facing angle.
// It can be used as attachment dummies for visual effects or as dummy caster.
//
// Why is recycling a unit important?
// Because creating a unit is is one of the slowest function in the game
// and there are reports that will always leave a permanent tiny bit of
// memory (0.04 KB).
// On average, retrieving a pending Dummy is approximately 4x faster compared
// to creating a new one and recycling a Dummy compared to removing it is
// approximately 1.3x faster.
// Furthermore, if you're using a lot of "Unit has entered map" events,
// using this system will even result to even more better performance
// because retrieving Dummy units does not cause that event to run.
*/ requires /*
nothing
*/ optional Table/*
if not found, this system will use a hashtable. Hashtables are limited to
255 per map.
*/ optional WorldBounds /*
if not found, this system will initialize its own Map Boundaries.
//
//
// Features:
//
// -- Dummy Sharing
// When a Dummy List gets low on unit count, it will borrow Dummy Units
// from the Dummy List with the highest unit count. The transfer is not
// instant because the shared Dummy Unit has to turn to the appropriate
// angle of its new Dummy List before it can be recycled.
// See BORROW_REQUEST.
//
// -- Self-balancing recycling algorithm
// Recycled Dummy Units will be thrown to the List having the least number
// of Dummy Units.
//
// -- Recycling least used
// Allows recycling a Dummy from the Dummy List with the highest
// unit count. It is useful when the facing angle of the Dummy Unit
// does not matter.
// See GetRecycledDummyAnyAngle.
//
// -- Self-adaptation
// When there are no free Dummy Units from a Dummy List, it will end up creating
// a new unit instead but that unit will be permanently added as a Dummy
// Unit to be recycled increasing the overall total Dummy Unit count.
//
// -- Count control
// Allows limiting the overall number of Dummy Units.
// See MAX_DUMMY_COUNT.
//
// -- Delayed Recycle
// Allows recycling Dummy Units after some delay to allocate time for the
// death animation of Special Effects to be seen.
// See DummyAddRecycleTimer.
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
// function GetRecycledDummy takes real x, real y, real z, real facing returns unit
// - Retrieve an unused Dummy Unit from the List.
// - The equivalent of CreateUnit.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
// function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
// - Use this function if the facing angle of the Dummy doesn't matter to you.
// - It will return a unit from the list having the highest number of unused Dummy Units.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
// function RecycleDummy takes unit u returns nothing
// - Recycle the Dummy unit for it to be used again later.
// - The equivalent of RemoveUnit.
//
// function DummyAddRecycleTimer takes unit u, real time returns nothing
// - Recycle the Dummy unit after a certain time.
// - Use this to allocate time for the the death animation of an effect attached to the
// Dummy Unit to finish..
// - The equivalent of UnitApplyTimedLife.
//
// function ShowDummy takes unit u, boolean flag returns nothing
// - Shows/hides Dummy Unit without conflicting with the Locust ability.
//
//--------------------
// CREDITS
//--------------------
// Bribe - for the MissileRecycler (vJASS) where I got this concept from
// http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
// - for the optional Table
// http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
// Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
// http://www.wc3c.net/showthread.php?t=101150
// Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
// http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
// Nestharus - for the data structure
// http://www.hiveworkshop.com/forums/2809461-post7.html
// - for the optional WorldBounds
// http://githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== */
/*
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i function dummy()
//! i setobjecttype("units")
//! i createobject("uloc", "dumi")
//! i makechange(current, "umdl", "dummy.mdx")
//! i end
//! i dummy()
//! endexternalblock
*/
globals
//The rawcode of the Dummy Unit
private constant integer DUMMY_ID = 'dumi'
//The owner of the Dummy Unit
private constant player OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
//The number of indexed angle. The higher the value the:
// - Lesser the turning time for the Dummy Units.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 10 (Max difference of 18 degrees)
private constant integer ANGLES_COUNT = 10
//The number of Dummy units per ANGLES_COUNT. The higher the value the:
// - Higher the number of units that can be recycled per angle, when
// no more units are in queue, the system will resort to use CreateUnit.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
private constant integer STORED_UNIT_COUNT = 3
//The maximum number of Dummy units that can exist. When the system resorts
//to using CreateUnit, the unit will be permanently added to the Dummy
//List. To avoid spamming Dummy Units and having too much free Dummy
//Units to allocate, the maximum number of Dummy Units is capped.
// Recommended Value: 80 to 120
private constant integer MAX_DUMMY_COUNT = 400
//When a certain angle have less than BORROW_REQUEST units in its list,
//it will start to borrow Dummy Units from the list with the highest
//Dummy Unit count.
// Recommended Value: Half of maximum STORED_UNIT_COUNT
private constant integer BORROW_REQUEST = 5
//It will only return a Dummy if the current dummy is close
//to it's appropriate facing angle. This is to avoid returning
//a Dummy which is still turning to face it's list angle.
private constant real ANGLE_TOLERANCE = 10.0
//An additional option to automatically hide recycled dummy units in the
//corner of the map camera bounds
private constant boolean HIDE_ON_MAP_CORNER = true
endglobals
//Every time a new dummy unit is retrieved, it will apply this resets
//If it is redundant/you dont need it, remove it.
//! textmacro DUMMY_UNIT_RESET
call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
call ShowDummy(bj_lastCreatedUnit, true)
if IsUnitPaused(bj_lastCreatedUnit) then
call PauseUnit(bj_lastCreatedUnit, false)
endif
//! endtextmacro
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
globals
private integer dummyCount = ANGLES_COUNT*STORED_UNIT_COUNT
private real array angle
private integer array count
private integer array countHead
private integer array countNext
private integer array countPrev
private integer array next
private integer array prev
private unit array dummy
private integer upper
private integer lower
private integer lastInstance
private constant real FACING_OFFSET = 180.0/ANGLES_COUNT
endglobals
static if HIDE_ON_MAP_CORNER and not LIBRARY_WorldBounds then
private module BoundsInit
readonly static real x
readonly static real y
private static method onInit takes nothing returns nothing
local rect map = GetWorldBounds()
set thistype.x = GetRectMaxX(map)
set thistype.y = GetRectMaxY(map)
call RemoveRect(map)
set map = null
endmethod
endmodule
private struct Bounds extends array
implement BoundsInit
endstruct
endif
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable hash = InitHashtable()
endif
private static method onInit takes nothing returns nothing
local real add = 360.0/ANGLES_COUNT
local real a = 0
local integer this = ANGLES_COUNT
local integer head = 0
local integer cHead = JASS_MAX_ARRAY_SIZE - 1 //avoid allocation collision
local integer i = R2I(MAX_DUMMY_COUNT/ANGLES_COUNT + 0.5)
set upper = STORED_UNIT_COUNT
set lower = STORED_UNIT_COUNT
static if LIBRARY_Table then
set tb = Table.create()
endif
//Initialize countHeads
loop
exitwhen i < 0
set countNext[cHead] = cHead
set countPrev[cHead] = cHead
set countHead[i] = cHead
set cHead = cHead - 1
set i = i - 1
endloop
set cHead = countHead[STORED_UNIT_COUNT] //All heads will be inserted here initially
//Create the Dummy units
loop
exitwhen a >= 360
//Initialize head
set next[head] = head
set prev[head] = head
set count[head] = STORED_UNIT_COUNT
set angle[head] = a
//Insert head in the Count List
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
set i = 0
loop
exitwhen i >= STORED_UNIT_COUNT
//Queued Linked List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, a)
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, Bounds.x, Bounds.y, a)
endif
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, 0, 0, a)
endif
call PauseUnit(dummy[this], true)
static if LIBRARY_Table then
set tb[GetHandleId(dummy[this])] = this
else
call SaveInteger(hash, GetHandleId(dummy[this]), 0, this)
endif
set this = this + 1
set i = i + 1
endloop
set head = head + 1
set a = a + add
endloop
set lastInstance = this
endmethod
endmodule
private struct S extends array
implement M
endstruct
private function GetHead takes integer facing returns integer
if facing < 0 or facing >= 360 then
set facing = facing - (facing/360)*360
if facing < 0 then
set facing = facing + 360
endif
endif
return R2I((facing*ANGLES_COUNT/360.0))
endfunction
function ShowDummy takes unit u, boolean flag returns nothing
if IsUnitHidden(u) == flag then
call ShowUnit(u, flag)
if flag and GetUnitTypeId(u) == DUMMY_ID then
call UnitRemoveAbility(u, 'Aloc')
call UnitAddAbility(u, 'Aloc')
endif
endif
endfunction
function GetRecycledDummy takes real x, real y, real z, real facing returns unit
local integer head = GetHead(R2I(facing + FACING_OFFSET))
local integer this = next[head]
local integer cHead
//If there are Dummy Units in the Queue List already facing close to the appropriate angle
if this != head and RAbsBJ(GetUnitFacing(dummy[this]) - angle[head]) <= ANGLE_TOLERANCE then
//Remove from the Queue List
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//For double free protection
set next[this] = -1
//Unit Properties
set bj_lastCreatedUnit = dummy[this]
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call SetUnitFacing(bj_lastCreatedUnit, facing)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
//! runtextmacro DUMMY_UNIT_RESET()
//Update Count and Bounds
set count[head] = count[head] - 1
//------------------------------------------------
// Unit Sharing
//------------------------------------------------
if count[head] < BORROW_REQUEST and count[countNext[countHead[upper]]] > count[head] then
set count[head] = count[head] + 1
set this = next[countNext[countHead[upper]]]
call SetUnitFacing(dummy[this], angle[head])
//Remove
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//Add to the Current List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
set head = countNext[countHead[upper]]
set count[head] = count[head] - 1
endif
//---------------------------
//Update Count Lists
//---------------------------
//Remove from the current Count List
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[upper]
if countNext[cHead] == cHead then
set upper = upper - 1
endif
if count[head] < lower then
set lower = count[head]
endif
else
set bj_lastCreatedUnit = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call PauseUnit(bj_lastCreatedUnit, true)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
if dummyCount < MAX_DUMMY_COUNT then
set this = lastInstance
//For double free protection
set next[this] = -1
set dummy[this] = bj_lastCreatedUnit
static if LIBRARY_Table then
set S.tb[GetHandleId(bj_lastCreatedUnit)] = this
else
call SaveInteger(S.hash, GetHandleId(bj_lastCreatedUnit), 0, this)
endif
set lastInstance = lastInstance + 1
endif
set dummyCount = dummyCount + 1
endif
return bj_lastCreatedUnit
endfunction
function RecycleDummy takes unit u returns nothing
static if LIBRARY_Table then
local integer this = S.tb[GetHandleId(u)]
else
local integer this = LoadInteger(S.hash, GetHandleId(u), 0)
endif
local integer head
local integer cHead
//If the unit is a legit Dummy Unit
if this > 0 and next[this] == -1 then
//Find where to insert based on the list having the least number of units
set head = countNext[countHead[lower]]
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
//Update Status
call SetUnitFacing(u, angle[head])
call PauseUnit(u, true)
call SetUnitOwner(u, OWNER, false)
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
call SetUnitX(u, WorldBounds.maxX)
call SetUnitY(u, WorldBounds.maxY)
else
call SetUnitX(u, Bounds.x)
call SetUnitY(u, Bounds.y)
endif
else
call SetUnitScale(u, 0, 0, 0)
call SetUnitVertexColor(u, 0, 0, 0, 0)
endif
set count[head] = count[head] + 1
//---------------------------
// Update Count Lists
//---------------------------
//Remove
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[lower]
if countNext[cHead] == cHead then
set lower = lower + 1
endif
if count[head] > upper then
set upper = count[head]
endif
elseif this == 0 then
call RemoveUnit(u)
debug elseif next[this] != -1 then
debug call BJDebugMsg("|cffffcc00[DummyRecycler]:|r Attempted to recycle a pending/free Dummy Unit.")
endif
endfunction
private function Expires takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
static if LIBRARY_Table then
call RecycleDummy(S.tb.unit[id])
call S.tb.unit.remove(id)
else
call RecycleDummy(LoadUnitHandle(S.hash, id, 0))
call FlushChildHashtable(S.hash, id)
endif
call DestroyTimer(t)
set t = null
endfunction
function DummyAddRecycleTimer takes unit u, real time returns nothing
local timer t = CreateTimer()
static if LIBRARY_Table then
set S.tb.unit[GetHandleId(t)] = u
else
call SaveUnitHandle(S.hash, GetHandleId(t), 0, u)
endif
call TimerStart(t, time, false, function Expires)
set t = null
endfunction
function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
return GetRecycledDummy(x, y, z, angle[countNext[countHead[upper]]])
endfunction
// runtextmacro DUMMY_DEBUG_TOOLS()
endlibrary
library TimerUtilsEx requires optional Table
/*************************************************
*
* TimerUtilsEx
* v2.1.0.2
* By Vexorian, Bribe & Magtheridon96
*
* Original version by Vexorian.
*
* Flavors:
* Hashtable:
* - RAM: Minimal
* - TimerData: Slow
*
* Array:
* - RAM: Maximal
* - TimerData: Fast
*
* All the functions have O(1) complexity.
* The Array version is the fastest, but the hashtable
* version is the safest. The Array version is still
* quite safe though, and I would recommend using it.
* The system is much slower in debug mode.
*
* Optional Requirement:
* - Table by Bribe
* - hiveworkshop.com/forums/showthread.php?t=188084
*
* API:
* ----
* - function NewTimer takes nothing returns timer
* - Returns a new timer from the stack.
* - function NewTimerEx takes integer i returns timer
* - Returns a new timer from the stack and attaches a value to it.
* - function ReleaseTimer takes timer t returns integer
* - Throws a timer back into the stack. Also returns timer data.
* - function SetTimerData takes timer t, integer value returns nothing
* - Attaches a value to a timer.
* - function GetTimerData takes timer t returns integer
* - Returns the attached value.
*
*************************************************/
// Configuration
globals
// Use hashtable, or fast array?
private constant boolean USE_HASH = true
// Max Number of Timers Held in Stack
private constant integer QUANTITY = 256
endglobals
globals
private timer array tT
private integer tN = 0
endglobals
private module Init
private static method onInit takes nothing returns nothing
static if not USE_HASH then
local integer i = QUANTITY
loop
set i = i - 1
set tT[i] = CreateTimer()
exitwhen i == 0
endloop
set tN = QUANTITY
elseif LIBRARY_Table then
set tb = Table.create()
endif
endmethod
endmodule
// JassHelper doesn't support static ifs for globals.
private struct Data extends array
static if not USE_HASH then
static integer array data
endif
static if LIBRARY_Table then
static Table tb = 0
else
static hashtable ht = InitHashtable()
endif
implement Init
endstruct
// Double free protection
private function ValidTimer takes integer i returns boolean
static if LIBRARY_Table then
return Data.tb.boolean[-i]
else
return LoadBoolean(Data.ht, i, 1)
endif
endfunction
private function Get takes integer id returns integer
debug if not ValidTimer(id) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[TimerUtils]Error: Tried to get data from invalid timer.")
debug endif
static if USE_HASH then
static if LIBRARY_Table then
return Data.tb[id]
else
return LoadInteger(Data.ht, id, 0)
endif
else
return Data.data[id - 0x100000]
endif
endfunction
private function Set takes integer id, integer data returns nothing
debug if not ValidTimer(id) then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[TimerUtils]Error: Tried to attach data to invalid timer.")
debug endif
static if USE_HASH then
static if LIBRARY_Table then
set Data.tb[id] = data
else
call SaveInteger(Data.ht, id, 0, data)
endif
else
set Data.data[id - 0x100000] = data
endif
endfunction
function SetTimerData takes timer t, integer data returns nothing
call Set(GetHandleId(t), data)
endfunction
function GetTimerData takes timer t returns integer
return Get(GetHandleId(t))
endfunction
function NewTimerEx takes integer data returns timer
local integer id
if tN == 0 then
static if USE_HASH then
set tT[0] = CreateTimer()
else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[TimerUtils]Error: No Timers In The Stack! You must increase 'QUANTITY'")
return null
endif
else
set tN = tN - 1
endif
set id = GetHandleId(tT[tN])
static if LIBRARY_Table then
set Data.tb.boolean[-id] = true
else
call SaveBoolean(Data.ht, id, 1, true)
endif
call Set(id, data)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
function ReleaseTimer takes timer t returns integer
local integer id = GetHandleId(t)
local integer data = 0
// Pause the timer just in case.
call PauseTimer(t)
// Make sure the timer is valid.
if ValidTimer(id) then
// Get the timer's data.
set data = Get(id)
// Unmark handle id as a valid timer.
static if LIBRARY_Table then
call Data.tb.boolean.remove(-id)
else
call RemoveSavedBoolean(Data.ht, id, 1)
endif
//If it's not run in USE_HASH mode, this next block is useless.
static if USE_HASH then
//At least clear hash memory while it's in the recycle stack.
static if LIBRARY_Table then
call Data.tb.remove(id)
else
call RemoveSavedInteger(Data.ht, id, 0)
endif
// If the recycle limit is reached
if tN == QUANTITY then
// then we destroy the timer.
call DestroyTimer(t)
return data
endif
endif
//Recycle the timer.
set tT[tN] = t
set tN = tN + 1
//Tried to pass a bad timer.
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[TimerUtils]Error: Tried to release non-active timer!")
endif
//Return Timer Data.
return data
endfunction
endlibrary
library TimerUtils requires TimerUtilsEx
endlibrary
library SelectionCircle initializer init
/* SelectionCircle
* - By Aniki
* - Documentation added out of good will.
*
* Link: https://www.hiveworkshop.com/threads/show-me-the-ranges.299681/#post-3201943
*
* ________
*
* API
* ________
*
* function get_rgba(integer i)
* Sets the globals R, G, B, A to their respective values. Integer i is a hex parameter.
*
* struct SelectionCircle
* boolean once = true
* string file_name
* image img
* real radius
* integer color
*
* real x
* real y
*
* unit target
* integer ul_pos = 0
*
* static SelectionCircle array update_list
* static integer update_list_len
* static code update_cb
* static timer ticker = CreateTimer()
*
* method set_radius(real radius)
* - Sets the radius of the circle.
*
* static method create(real x, real y, real radius, integer color) returns SelectionCircle
* - Constructs a SelectionCircle instance.
*
* static method create_xy(real x, real y, real radius, integer color) returns SelectionCircle
* - A wrapper for the constructor above.
*
* static method attach_to_unit(unit u, real radius, integer color) returns SelectionCircle
* - Internally calls create_xy and binds it to a list, of which will be iterated over.
*
* method destroy()
* - Destroys a SelectionCircle instance.
*
* method set_pos(real x, real y)
* - Sets the position of a circular object of a SelectionCircle instance.
*
* method set_dotted(boolean flag)
* - Modifies the external properties of the circular object.
*
* Added: (Did not originally exist)
* private trigger exec_trig
* private triggercondition exec
* private integer data
* private boolean visible
*
* static SelectionCircle current
*
* method set_listener_event(code exe)
* - Removes a previous set of instructions and adds a new set of instructions
* to the listening trigger instance.
*
* method set_show(boolean flag)
* - Shows or hides a SelectionCircle (untested).
*
* End API description.
*/
globals
// SelectionCircle(s) attached to units are updated this frequently
private constant real Update_Delay = 1.0 / 32.0
endglobals
globals
private integer R
private integer G
private integer B
private integer A
endglobals
private function get_rgba takes integer i returns nothing
local boolean is_neg = false
if i < 0 then
set is_neg = true
set i = i + 0x80000000
endif
set A = i - i / 0x100 * 0x100
set i = i / 0x100
set B = i - i / 0x100 * 0x100
set i = i / 0x100
set G = i - i / 0x100 * 0x100
set i = i / 0x100
set R = i
if is_neg then
set R = R + 0x80
endif
endfunction
struct SelectionCircle
// Added for event listening.
trigger exec_trig
triggercondition exec
boolean visible
integer data
boolean once = true
string file_name
image img
real radius
integer color
real x
real y
unit target
integer ul_pos = 0
method set_radius takes real radius returns nothing
local SelectionCircle sc = this
set sc.radius = radius
// we can't use the check
// if sc.img != null
// because the first image's handle-id is 0 (gj blizzard...)
// and 0 == null :/
if sc.once then
set sc.once = false
else
call DestroyImage(sc.img)
endif
set sc.img = CreateImage(sc.file_name, /*
image-width, image-height:*/ 2.0*radius, 2.0*radius, 0.0, /*
image-position: */ sc.x, sc.y, 0.0, /*
origin: */ radius, radius, 0.0, /*
image-type: */ 1 /*
*/)
call SetImageRenderAlways(sc.img, true)
call get_rgba(sc.color)
call SetImageColor(sc.img, R, G, B, A)
endmethod
static method create takes real x, real y, real radius, integer color returns SelectionCircle
local SelectionCircle sc = SelectionCircle.allocate()
set sc.file_name = "ReplaceableTextures\\Selection\\SelectionCircleLarge.blp"
set sc.x = x
set sc.y = y
set sc.color = color
set sc.data = 0
call sc.set_radius(radius)
return sc
endmethod
static method create_xy takes real x, real y, real radius, integer color returns SelectionCircle
return create(x, y, radius, color)
endmethod
static SelectionCircle array update_list
static SelectionCircle current = 0
static integer update_list_len = 0
static code update_cb
static timer ticker = CreateTimer()
// Modified to accomodate the event listener
static method attach_to_unit takes unit u, real radius, integer color returns SelectionCircle
local SelectionCircle sc = create_xy(GetUnitX(u), GetUnitY(u), radius, color)
set sc.target = u
set sc.exec_trig = CreateTrigger()
set update_list_len = update_list_len + 1
set update_list[update_list_len] = sc
set sc.ul_pos = update_list_len
if update_list_len == 1 then
call TimerStart(ticker, Update_Delay, true, update_cb)
endif
return sc
endmethod
method destroy takes nothing returns nothing
local SelectionCircle sc = this
local SelectionCircle last
call DestroyImage(sc.img)
if sc.ul_pos != 0 then
call DestroyTrigger(exec_trig)
set sc.target = null
set last = update_list[update_list_len]
set last.ul_pos = sc.ul_pos
set update_list[last.ul_pos] = last
set update_list_len = update_list_len - 1
if update_list_len == 0 then
call PauseTimer(ticker)
endif
endif
set exec = null
call sc.deallocate()
endmethod
static method update takes nothing returns nothing
local integer i
local SelectionCircle sc
set i = 1
loop
exitwhen i > update_list_len
set SelectionCircle.current = update_list[i]
set sc = SelectionCircle.current
set sc.x = GetUnitX(sc.target)
set sc.y = GetUnitY(sc.target)
call SetImagePosition(sc.img, sc.x, sc.y, 0.0)
call TriggerEvaluate(sc.exec_trig)
set i = i + 1
endloop
endmethod
method set_color takes integer color returns nothing
local SelectionCircle sc = this
set sc.color = color
call get_rgba(color)
call SetImageColor(sc.img, R, G, B, A)
endmethod
method set_pos takes real x, real y returns nothing
local SelectionCircle sc = this
set sc.x = x
set sc.y = y
call SetImagePosition(sc.img, x, y, 0.0)
endmethod
method set_dotted takes boolean flag returns nothing
local SelectionCircle sc = this
if flag then
set sc.file_name = "ReplaceableTextures\\Selection\\SelectionCircleLargeDotted.blp"
else
set sc.file_name = "ReplaceableTextures\\Selection\\SelectionCircleLarge.blp"
endif
call sc.set_radius(sc.radius)
endmethod
method set_listener_event takes code exe returns nothing
local SelectionCircle sc = this
if sc.ul_pos != 0 then
call TriggerRemoveCondition(sc.exec_trig, sc.exec)
set exec = TriggerAddCondition(sc.exec_trig, Condition(exe))
endif
endmethod
method set_show takes boolean flag returns nothing
local SelectionCircle sc = this
call SetImageRenderAlways(sc.img, flag)
endmethod
method set_show_player takes player p, boolean flag returns nothing
local SelectionCircle sc = this
if GetLocalPlayer() == p then
call SetImageRenderAlways(sc.img, flag)
endif
endmethod
endstruct
private function init takes nothing returns nothing
set SelectionCircle.update_cb = function SelectionCircle.update
endfunction
endlibrary
library TimedEffect requires TimerUtils, Table
globals
private Table destFlagMap = 0
endglobals
private module Init
private static method onInit takes nothing returns nothing
set destFlagMap = Table.create()
endmethod
endmodule
private struct S extends array
implement Init
endstruct
private function OnDestroyEffect takes nothing returns nothing
local integer id = ReleaseTimer(GetExpiredTimer())
local effect whichEffect = destFlagMap.effect[id]
call destFlagMap.effect.remove(id)
call DestroyEffect(whichEffect)
set whichEffect = null
endfunction
function DestroyEffectTimed takes effect whichEffect, real duration returns nothing
local integer id = GetHandleId(whichEffect)
if duration <= 0 then
call DestroyEffect(whichEffect)
return
elseif destFlagMap.effect.has(id) then
return
endif
set destFlagMap.effect[id] = whichEffect
call TimerStart(NewTimerEx(id), duration, false, function OnDestroyEffect)
endfunction
endlibrary
library BezierEasing /* 1.0.0
*************************************************************************************
*
* Build Cubic Bezier-based Easing functions
*
* Instead of solving for the point on the cubic bezier curve, BezierEasing
* solves for output Y where X is the input.
*
* Useful for adjusting animation rate smoothness
*
*************************************************************************************
*
* struct BezierEasing extends array
*
* static method create takes real ax, real ay, real bx, real by returns thistype
* - points (ax, ay) and (bx, by) are cubic bezier control points on 2D plane.
* - cx = cubic(0, ax, bx, 1)
* - cy = cubic(0, ay, by, 1)
* method operator [] takes real t returns real
* - real "t" is the given time progression whose value in [0..1] range
*
*************************************************************************************/
globals
/*
* Adjust precision of epsilon's value
* higher precision = lower performance
*
* May cause infinite loop if the
* precision is too high.
*/
private constant real EPSILON = 0.00001
endglobals
private function Abs takes real a returns real
if(a < 0) then
return -a
endif
return a
endfunction
private function Max takes real a, real b returns real
if(a < b) then
return b
endif
return a
endfunction
/*
* Float Equality Approximation
* Accuracy is influenced by EPSILON's value
*/
private function Equals takes real a, real b returns boolean
return Abs(a - b) <= EPSILON*Max(1., Max(Abs(a), Abs(b)))
endfunction
private function Bezier3 takes real a, real b, real c, real d, real t returns real
local real x = 1. - t
return x*x*x*a + 3*x*x*t*b + 3*x*t*t*c + t*t*t*d
endfunction
private module Init
private static method onInit takes nothing returns nothing
call init()
endmethod
endmodule
struct BezierEasing extends array
private static thistype array r
private real x1
private real y1
private real x2
private real y2
static method create takes real ax, real ay, real bx, real by returns thistype
local thistype this = r[0]
if(r[this] == 0) then
set r[0] = this + 1
else
set r[0] = r[this]
endif
set r[this] = -1
set x1 = ax
set y1 = ay
set x2 = bx
set y2 = by
return this
endmethod
method operator [] takes real t returns real
/*
* Perform binary search for the equivalent points on curve
* by using the t factor of cubic beziers, where the input
* is equal to the bezier point's x, and the output is the
* point's y, respectively.
*/
local real lo = 0.
local real hi = 1.
local real mid
local real tx
local real ty
local real ax = x1
local real ay = y1
local real bx = x2
local real by = y2
/*
* Since bezier points lies within
* the [0, 1] bracket, just return
* the bound values.
*/
if(Equals(t, 0.)) or (t < 0.) then
return 0.
elseif(Equals(t, 1.)) or (t > 1.) then
return 1.
endif
/*
* Binary Search
*/
loop
set mid = (lo + hi)*0.5
set tx = Bezier3(0, ax, bx, 1, mid)
set ty = Bezier3(0, ay, by, 1, mid)
if(Equals(t, tx))then
return ty
elseif(t < tx) then
set hi = mid
else
set lo = mid
endif
endloop
return 0.
endmethod
method destroy takes nothing returns nothing
if(r[this] == -1) then
set r[this] = r[0]
set r[0] = this
set x1 = 0.
set y1 = 0.
set x2 = 0.
set y2 = 0.
endif
endmethod
private static method init takes nothing returns nothing
set r[0] = 1
endmethod
implement Init
endstruct
struct BezierEase extends array
readonly static BezierEasing inQuad
readonly static BezierEasing outQuad
readonly static BezierEasing inOutQuad
readonly static BezierEasing inCubic
readonly static BezierEasing outCubic
readonly static BezierEasing inOutCubic
readonly static BezierEasing inQuart
readonly static BezierEasing outQuart
readonly static BezierEasing inOutQuart
readonly static BezierEasing inQuint
readonly static BezierEasing outQuint
readonly static BezierEasing inOutQuint
readonly static BezierEasing inSine
readonly static BezierEasing outSine
readonly static BezierEasing inOutSine
readonly static BezierEasing inBack
readonly static BezierEasing outBack
readonly static BezierEasing inOutBack
readonly static BezierEasing inCirc
readonly static BezierEasing outCirc
readonly static BezierEasing inOutCirc
readonly static BezierEasing inExpo
readonly static BezierEasing outExpo
readonly static BezierEasing inOutExpo
private static method init takes nothing returns nothing
set inSine = BezierEasing.create(0.47, 0, 0.745, 0.715)
set outSine = BezierEasing.create(0.39, 0.575, 0.565, 1)
set inOutSine = BezierEasing.create(0.445, 0.05, 0.55, 0.95)
set inQuad = BezierEasing.create(0.55, 0.085, 0.68, 0.53)
set outQuad = BezierEasing.create(0.25, 0.46, 0.45, 0.94)
set inOutQuad = BezierEasing.create(0.455, 0.03, 0.515, 0.955)
set inCubic = BezierEasing.create(0.55, 0.055, 0.675, 0.19)
set outCubic = BezierEasing.create(0.215, 0.61, 0.355, 1)
set inOutCubic = BezierEasing.create(0.645, 0.045, 0.355, 1)
set inQuart = BezierEasing.create(0.895, 0.03, 0.685, 0.22)
set outQuart = BezierEasing.create(0.165, 0.84, 0.44, 1)
set inOutQuart = BezierEasing.create(0.77, 0, 0.175, 1)
set inQuint = BezierEasing.create(0.755, 0.05, 0.855, 0.06)
set outQuint = BezierEasing.create(0.23, 1, 0.32, 1)
set inOutQuint = BezierEasing.create(0.86, 0, 0.07, 1)
set inExpo = BezierEasing.create(0.95, 0.05, 0.795, 0.035)
set outExpo = BezierEasing.create(0.19, 1, 0.22, 1)
set inOutExpo = BezierEasing.create(1, 0, 0, 1)
set inCirc = BezierEasing.create(0.6, 0.04, 0.98, 0.335)
set outCirc = BezierEasing.create(0.075, 0.82, 0.165, 1)
set inOutCirc = BezierEasing.create(0.785, 0.135, 0.15, 0.86)
set inBack = BezierEasing.create(0.6, -0.28, 0.735, 0.045)
set outBack = BezierEasing.create(0.175, 0.885, 0.32, 1.275)
set inOutBack = BezierEasing.create(0.68, -0.55, 0.265, 1.55)
endmethod
implement Init
endstruct
endlibrary
library LinkedList /* v1.3.0 https://www.hiveworkshop.com/threads/linkedlist-modules.325635/
*/uses /*
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
/*
Author:
- AGD
Credits:
- Nestharus, Dirac, Bribe
> For their scripts and discussions which I used as reference
Pros:
- Feature-rich (Can be)
- Modular
- Safety-oriented (On DEBUG_MODE, but not 100% fool-proof ofcourse)
- Flexible (Does not enforce a built-in allocator - allows user to choose between a custom Alloc
or the default vjass allocator, or neither)
- Extensible (Provides interfaces)
Note:
If you are using using Dirac's 'LinkedListModule' library, you need to replace its contents with
the compatibility lib provided alongside this library for all to work seamlessly.
*/
|-----|
| API |
|-----|
/*
Note: All the fields except from 'prev' and 'next' are actually operators, so you might want to
avoid using them from the interface methods that would be declared above them.
=====================================================================================================
List Fields Modules (For those who want to write or inline the core linked-list operations themselves)
*/module LinkedListFields/*
*/readonly thistype prev/*
*/readonly thistype next/*
*/module StaticListFields extends LinkedListFields/*
*/readonly static constant thistype sentinel/*
*/readonly static thistype front/*
*/readonly static thistype back/*
*/readonly static boolean empty/*
*/module ListFields extends LinkedListFields/*
*/readonly thistype front/*
*/readonly thistype back/*
*/readonly boolean empty/*
=====================================================================================================
Lite List Modules (Should be enough for most cases)
*/module LinkedListLite extends LinkedListFields/*
*/optional interface static method onInsert takes thistype node returns nothing/*
*/optional interface static method onRemove takes thistype node returns nothing/*
*/optional interface method onTraverse takes thistype node returns boolean/*
*/static method insert takes thistype prev, thistype node returns nothing/*
*/static method remove takes thistype node returns nothing/*
*/method traverseForwards takes nothing returns nothing/*
*/method traverseBackwards takes nothing returns nothing/*
- Only present if onTraverse() is also present
*/module StaticListLite extends StaticListFields, LinkedListLite/*
*/static method pushFront takes thistype node returns nothing/*
*/static method popFront takes nothing returns thistype/*
*/static method pushBack takes thistype node returns nothing/*
*/static method popBack takes nothing returns thistype/*
*/module ListLite extends ListFields, LinkedListLite/*
*/method pushFront takes thistype node returns nothing/*
*/method popFront takes nothing returns thistype/*
*/method pushBack takes thistype node returns nothing/*
*/method popBack takes nothing returns thistype/*
*/module InstantiatedListLite extends ListLite/*
*/interface static method allocate takes nothing returns thistype/*
*/interface method deallocate takes nothing returns nothing/*
*/optional interface method onConstruct takes nothing returns nothing/*
*/optional interface method onDestruct takes nothing returns nothing/*
*/static method create takes nothing returns thistype/*
*/method destroy takes nothing returns nothing/*
=====================================================================================================
Standard List Modules
*/module LinkedList extends LinkedListLite/*
*/static method isLinked takes thistype node returns boolean/*
*/module StaticList extends StaticListLite, LinkedList/*
*/static method clear takes nothing returns nothing/*
*/static method flush takes nothing returns nothing/*
*/module List extends ListLite, LinkedList/*
*/static method makeHead takes thistype node returns nothing/*
*/method clear takes nothing returns nothing/*
*/method flush takes nothing returns nothing/*
*/module InstantiatedList extends InstantiatedListLite, List/*
=====================================================================================================
Feature-rich List Modules (For those who somehow need exotic linked-list operations)
*/module LinkedListEx extends LinkedList/*
*/static method move takes thistype prev, thistype node returns nothing/*
*/static method swap takes thistype nodeA, thistype nodeB returns nothing/*
*/module StaticListEx extends StaticList, LinkedListEx/*
*/static method contains takes thistype node returns boolean/*
*/static method getSize takes nothing returns integer/*
*/static method rotateLeft takes nothing returns nothing/*
*/static method rotateRight takes nothing returns nothing/*
*/module ListEx extends List, LinkedListEx/*
*/method contains takes thistype node returns boolean/*
*/method getSize takes nothing returns integer/*
*/method rotateLeft takes nothing returns nothing/*
*/method rotateRight takes nothing returns nothing/*
*/module InstantiatedListEx extends InstantiatedList, ListEx/*
*///! endnovjass
/*========================================= CONFIGURATION ===========================================
* Only affects DEBUG_MODE
* If false, throws warnings instead (Errors pauses the game (Or stops the thread) while warnings do not)
*/
globals
private constant boolean THROW_ERRORS = true
endglobals
/*====================================== END OF CONFIGURATION =====================================*/
static if DEBUG_MODE then
public function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
static if THROW_ERRORS then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
else
call ThrowWarning(condition, SCOPE_PREFIX, methodName, structName, node, message)
endif
else
if condition then
static if THROW_ERRORS then
call BJDebugMsg("|cffff0000[ERROR]|r [Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
call PauseGame(true)
else
call BJDebugMsg("|cffffcc00[WARNING]|r [Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffffcc00" + message + "|r")
endif
endif
endif
endfunction
endif
private module LinkedListUtils
method p_clear takes nothing returns nothing
set this.next.prev = 0
set this.prev.next = 0
set this.prev = this
set this.next = this
endmethod
method p_flush takes nothing returns nothing
local thistype node = this.prev
loop
exitwhen node == this
call remove(node)
set node = node.prev
endloop
endmethod
endmodule
private module LinkedListUtilsEx
implement LinkedListUtils
method p_contains takes thistype toFind returns boolean
local thistype node = this.next
loop
exitwhen node == this
if node == toFind then
return true
endif
set node = node.next
endloop
return false
endmethod
method p_getSize takes nothing returns integer
local integer count = 0
local thistype node = this.next
loop
exitwhen node == this
set count = count + 1
set node = node.next
endloop
return count
endmethod
endmodule
private module LinkedListLiteBase
implement LinkedListFields
debug method p_isEmptyHead takes nothing returns boolean
debug return this == this.next and this == this.prev
debug endmethod
static method p_insert takes thistype this, thistype node returns nothing
local thistype next = this.next
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
endmethod
static method p_remove takes thistype node returns nothing
set node.next.prev = node.prev
set node.prev.next = node.next
endmethod
static method insert takes thistype this, thistype node returns nothing
debug call AssertError(node == 0, "insert()", "thistype", 0, "Cannot insert null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "insert()", "thistype", 0, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call p_insert(this, node)
static if thistype.onInsert.exists then
call onInsert(node)
endif
endmethod
static method remove takes thistype node returns nothing
debug call AssertError(node == 0, "remove()", "thistype", 0, "Cannot remove null node")
debug call AssertError(node.next.prev != node and node.prev.next != node, "remove()", "thistype", 0, "Invalid node [" + I2S(node) + "]")
static if thistype.onRemove.exists then
call onRemove(node)
endif
call p_remove(node)
endmethod
static if thistype.onTraverse.exists then
method p_traverse takes boolean forward returns nothing
local thistype node
local thistype next
debug local thistype prev
debug local boolean array traversed
if forward then
set node = this.next
loop
exitwhen node == this or node.prev.next != node
debug call AssertError(traversed[node], "traverseForwards()", "thistype", this, "A node was traversed twice in a single traversal")
debug set traversed[node] = true
debug set prev = node.prev
set next = node.next
if this.onTraverse(node) then
call remove(node)
debug set traversed[node] = false
debug elseif next.prev == prev then
debug set traversed[node] = false
endif
set node = next
endloop
else
set node = this.prev
loop
exitwhen node == this or node.next.prev != node
debug call AssertError(traversed[node], "traverseBackwards()", "thistype", this, "A node was traversed twice in a single traversal")
debug set traversed[node] = true
debug set prev = node.next
set next = node.prev
if this.onTraverse(node) then
call remove(node)
debug set traversed[node] = false
debug elseif next.prev == prev then
debug set traversed[node] = false
endif
set node = next
endloop
endif
endmethod
method traverseForwards takes nothing returns nothing
call this.p_traverse(true)
endmethod
method traverseBackwards takes nothing returns nothing
call this.p_traverse(false)
endmethod
endif
endmodule
private module LinkedListBase
implement LinkedListLiteBase
static method isLinked takes thistype node returns boolean
return node.next.prev == node or node.prev.next == node
endmethod
endmodule
module LinkedListFields
readonly thistype prev
readonly thistype next
endmodule
module LinkedListLite
implement LinkedListLiteBase
implement optional LinkedListLiteModuleCompatibility // For API compatibility with Dirac's 'LinkedListModule' library
endmodule
module LinkedList
implement LinkedListBase
implement optional LinkedListModuleCompatibility // For API compatibility with Dirac's 'LinkedListModule' library
endmodule
module LinkedListEx
implement LinkedListBase
static method p_move takes thistype prev, thistype node returns nothing
call p_remove(node)
call p_insert(prev, node)
endmethod
static method move takes thistype prev, thistype node returns nothing
debug call AssertError(not isLinked(node), "move()", "thistype", 0, "Cannot use unlinked node [" + I2S(node) + "]")
call p_move(prev, node)
endmethod
static method swap takes thistype this, thistype node returns nothing
local thistype thisPrev = this.prev
local thistype thisNext = this.next
debug call AssertError(this == 0, "swap()", "thistype", 0, "Cannot swap null node")
debug call AssertError(node == 0, "swap()", "thistype", 0, "Cannot swap null node")
debug call AssertError(not isLinked(this), "swap()", "thistype", 0, "Cannot use unlinked node [" + I2S(this) + "]")
debug call AssertError(not isLinked(node), "swap()", "thistype", 0, "Cannot use unlinked node [" + I2S(node) + "]")
call p_move(node, this)
if thisNext != node then
call p_move(thisPrev, node)
endif
endmethod
endmodule
module StaticListFields
implement LinkedListFields
static constant method operator head takes nothing returns thistype
return 0
endmethod
static method operator back takes nothing returns thistype
return head.prev
endmethod
static method operator front takes nothing returns thistype
return head.next
endmethod
static method operator empty takes nothing returns boolean
return front == head
endmethod
endmodule
module StaticListLite
implement StaticListFields
implement LinkedListLiteBase
static method pushFront takes thistype node returns nothing
debug call AssertError(node == 0, "pushFront()", "thistype", 0, "Cannot use null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushFront()", "thistype", 0, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(head, node)
endmethod
static method popFront takes nothing returns thistype
local thistype node = front
debug call AssertError(node.prev != head, "popFront()", "thistype", 0, "Invalid list")
call remove(node)
return node
endmethod
static method pushBack takes thistype node returns nothing
debug call AssertError(node == 0, "pushBack()", "thistype", 0, "Cannot use null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushBack()", "thistype", 0, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(back, node)
endmethod
static method popBack takes nothing returns thistype
local thistype node = back
debug call AssertError(node.next != head, "popBack()", "thistype", 0, "Invalid list")
call remove(node)
return node
endmethod
endmodule
module StaticList
implement StaticListLite
implement LinkedListBase
implement LinkedListUtils
static method clear takes nothing returns nothing
call head.p_clear()
endmethod
static method flush takes nothing returns nothing
call head.p_flush()
endmethod
endmodule
module StaticListEx
implement StaticList
implement LinkedListEx
implement LinkedListUtilsEx
static method contains takes thistype node returns boolean
return head.p_contains(node)
endmethod
static method getSize takes nothing returns integer
return head.p_getSize()
endmethod
static method rotateLeft takes nothing returns nothing
call p_move(back, front)
endmethod
static method rotateRight takes nothing returns nothing
call p_move(head, back)
endmethod
endmodule
module ListFields
implement LinkedListFields
method operator back takes nothing returns thistype
return this.prev
endmethod
method operator front takes nothing returns thistype
return this.next
endmethod
method operator empty takes nothing returns boolean
return this.next == this
endmethod
endmodule
module ListLite
implement ListFields
implement LinkedListLiteBase
method pushFront takes thistype node returns nothing
debug call AssertError(this == 0, "pushFront()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "pushFront()", "thistype", this, "Invalid list")
debug call AssertError(node == 0, "pushFront()", "thistype", this, "Cannot insert null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushFront()", "thistype", this, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(this, node)
endmethod
method popFront takes nothing returns thistype
local thistype node = this.next
debug call AssertError(this == 0, "popFront()", "thistype", 0, "Null list")
debug call AssertError(node.prev != this, "popFront()", "thistype", this, "Invalid list")
call remove(node)
return node
endmethod
method pushBack takes thistype node returns nothing
debug call AssertError(this == 0, "pushBack()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "pushBack()", "thistype", this, "Invalid list")
debug call AssertError(node == 0, "pushBack()", "thistype", this, "Cannot insert null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushBack()", "thistype", this, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(this.prev, node)
endmethod
method popBack takes nothing returns thistype
local thistype node = this.prev
debug call AssertError(this == 0, "popBack()", "thistype", 0, "Null list")
debug call AssertError(node.next != this, "pushFront()", "thistype", this, "Invalid list")
call remove(node)
return node
endmethod
endmodule
module List
implement ListLite
implement LinkedListBase
implement LinkedListUtils
static method makeHead takes thistype node returns nothing
set node.prev = node
set node.next = node
endmethod
method clear takes nothing returns nothing
debug call AssertError(this == 0, "clear()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "clear()", "thistype", this, "Invalid list")
call this.p_clear()
endmethod
method flush takes nothing returns nothing
debug call AssertError(this == 0, "flush()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "flush()", "thistype", this, "Invalid list")
call this.p_flush()
endmethod
endmodule
module ListEx
implement List
implement LinkedListEx
implement LinkedListUtilsEx
method contains takes thistype node returns boolean
debug call AssertError(this == 0, "contains()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "contains()", "thistype", this, "Invalid list")
return this.p_contains(node)
endmethod
method getSize takes nothing returns integer
debug call AssertError(this == 0, "getSize()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "getSize()", "thistype", this, "Invalid list")
return this.p_getSize()
endmethod
method rotateLeft takes nothing returns nothing
debug call AssertError(this == 0, "rotateLeft()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "rotateLeft()", "thistype", this, "Invalid list")
call p_move(this.back, this.front)
endmethod
method rotateRight takes nothing returns nothing
debug call AssertError(this == 0, "rotateRight()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev == this, "rotateRight()", "thistype", this, "Invalid list")
call p_move(this, this.back)
endmethod
endmodule
module InstantiatedListLite
implement ListLite
debug private boolean valid
static method create takes nothing returns thistype
local thistype node = allocate()
set node.prev = node
set node.next = node
debug set node.valid = true
static if thistype.onConstruct.exists then
call node.onConstruct()
endif
return node
endmethod
method destroy takes nothing returns nothing
debug call AssertError(this == 0, "destroy()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "destroy()", "thistype", this, "Invalid list")
debug call AssertError(not this.valid, "destroy()", "thistype", this, "Double-free")
debug set this.valid = false
static if thistype.flush.exists then
call this.flush()
endif
static if thistype.onDestruct.exists then
call this.onDestruct()
endif
debug set this.prev = 0
debug set this.next = 0
call this.deallocate()
endmethod
endmodule
module InstantiatedList
implement List
implement InstantiatedListLite
endmodule
module InstantiatedListEx
implement ListEx
implement InstantiatedList
endmodule
endlibrary
library Missile /* version 3.0.2 (Unofficial Update for patches 1.30+)
Unofficial updates link: https://www.hiveworkshop.com/threads/missile.265370/page-13#post-3429957
*************************************************************************************
*
* Creating custom projectiles in Warcraft III.
*
* Major goal:
* No unessary external requirements.
* Implements code optional.
*
* Philosophy:
* I want that feature --> Compiler writes that code into your map script.
* I don't want that --> Compiler ignores that code completely.
*
* Important:
* Take yourself 2 minutes time to setup Missile correctly.
* Otherwise I can't guarantee, that Missile works the way you want.
* Once the setup is done, you can check out some examples and Missile will be easy
* to use for everyone. I promise it.
*
* Do the setup at:
*
* 1.) Import instruction
* 2.) Global configuration
* 3.) Function configuration
*
* Credits to Dirac, emjlr3, AceHart, Bribe, Wietlol,
* Nestharus, Maghteridon96, Vexorian and Zwiebelchen.
*
*************************************************************************************
*
* */ requires /*
*
* */ Table /* https://www.hiveworkshop.com/threads/188084/
*
* */ LinkedList /* https://www.hiveworkshop.com/threads/325635/
* - Make the vJass code shorter and more readable
*
*************************************************************************************
*
* Optional requirements listed can reduce overall code generation,
* add safety mechanisms, decrease overhead and optimize handle management.
* For a better overview I put them into blocks.
*
* I recommend to use at least one per block in your map.
*
* a) For best debug results: ( Useful )
* */ optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*
* b) Fatal error protection: ( Case: unit out moves of world bounds )
* - WorldBounds is safer than BoundSentinel.
* - WorldBounds adds more overhead than BoundSentinel.
* - Using none of these two forces Missile to switch from SetUnitX/Y to the SetUnitPosition native.
* */ optional WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
* */ optional BoundSentinel /* wc3c.net/showthread.php?t=102576
*
* c) Unit indexing: ( Avoid an onIndex event )
* - not required for Missile. Only if you use one already.
* */ optional UnitIndexer /* github.com/nestharus/JASS/tree/master/jass/Systems/Unit%20Indexer
*
* d) Allocator:
* - Reduces generated codes and variables
* - Takes advantage of the new JASS_MAX_ARRAY_SIZE value for patches 1.29+
* */ optional Alloc /* https://www.hiveworkshop.com/threads/324937/
*
* e) Game version detection:
* - Helps fix bugs introduced in 1.30.x related to new SFX natives
* */ optional GameVersion /* https://www.hiveworkshop.com/threads/308204/
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* • Copy Missile into to your map.
* • You need a dummy unit, using Vexorians "dummy.mdx".
* This unit must use the locust and crow form ability. ( Aloc & Amrf )
* ¯¯¯¯
*
* 2. Global configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Seven constants to setup!
*/
globals
/**
* Missiles are moved periodically. An interval of 1./32. is recommended.
* • Too short timeout intervals may cause performance issues.
* • Too large timeout intervals may look fishy.
*/
public constant real TIMER_TIMEOUT = 1./32.
/**
* Owner of all Missile dummies. Should be a neutral player in your map.
*
* (Depreciated in version 3.0.0)
*/
public constant player NEUTRAL_PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
/**
* Raw code of the dummy unit. Object Editor ( F6 )
* • Must be correct, otherwise missile dummies can neither be recycled nor destroyed.
* • Units of other type ids will not be thrown into the recycler bin.
*
* (Depreciated in version 3.0.0)
*/
public constant integer DUMMY_UNIT_ID = 'dumi'
/**
* The maximum collision size used in your map. If unsure use 197. ( Town hall collision )
* • Applies for all types of widgets.
* • A precise value can improve Missile's performance,
* since smaller values enumerate less widgtes per loop per missile.
*/
public constant real MAXIMUM_COLLISION_SIZE = 197.
/**
* Collision types for missiles. ( Documentation only )
* Missile decides internally each loop which type of collision is required.
* • Uses circular collision dectection for speed < collision. ( Good accuracy, best performance )
* • Uses rectangle collision for speed >= collision. ( Best accuracy, normal performance )
*/
public constant integer COLLISION_TYPE_CIRCLE = 0
public constant integer COLLISION_TYPE_RECTANGLE = 1
/**
* Determine when rectangle collision is required to detect nearby widgets.
* • The smaller the factor the earlier rectangle collision is used. ( by default 1. )
* • Formula: speed >= collision*Missile_COLLISION_ACCURACY_FACTOR
*/
public constant real COLLISION_ACCURACY_FACTOR = 1.
// Optional toogles:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Set booleans listed below to "true" and the compiler will write
// the feature into your map. Otherwise this code is completly ignored.
// • Yay, I want that --> "true"
// • Naah that's useless for me --> "false"
/**
* USE_COLLISION_Z_FILTER enables z axis checks for widget collision. ( Adds minimal overhead )
* Use it when you need:
* • Missiles flying over or under widgets.
* • Determine between flying and walking units.
*/
public constant boolean USE_COLLISION_Z_FILTER = true
/**
* WRITE_DELAYED_MISSILE_RECYCLING enables a delayed dummy recycling system. ( Very recommended )
* Use it if:
* • You use a dummy recycling library like MissileRecycler, Dummy or xedummy.
* • You want to properly display death animations of effects added to missiles.
*
* (Depreciated in version 3.0.0)
*/
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING = true
/**
* DELAYED_MISSILE_DEATH_ANIMATION_TIME is the delay in seconds
* Missile holds back a dummy, before recycling it.
* • The time value does not have to be precise.
* • Requires WRITE_DELAYED_MISSILE_RECYCLING = true
*
* (Depreciated in version 3.0.0)
*/
private constant real DELAYED_MISSILE_DEATH_ANIMATION_TIME = 2.
/**
* USE_DESTRUCTABLE_FILTER and USE_ITEM_FILTER are redundant constants from previous Missile versions.
* They do nothing, but remain for backwards compatibilty.
* From Missile version 1.5 on all widget collisions are always enabled.
*/
public constant boolean USE_DESTRUCTABLE_FILTER = true
public constant boolean USE_ITEM_FILTER = true
endglobals
/*
* 3. Function configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Four functions to setup!
*/
/**
* GetUnitBodySize(unit) returns a fictional value for z - axis collision.
* You have two options:
* • One constant value shared over all units.
* • Dynamic values based on handle id, type id, unit user data, scaling or other parameters.
*/
function GetUnitBodySize takes unit whichUnit returns real
return 100.// Other example: return LoadReal(hash, GetHandleId(whichUnit), KEY_UNIT_BODY_SIZE)
endfunction
/**
* Same as GetUnitBodySize, but for destructables.
* Using occluder height is an idea of mine. Of course you can use your own values.
*/
function GetDestructableHeight takes destructable d returns real
return GetDestructableOccluderHeight(d)// Other example: return 100.
endfunction
/**
* Same as GetUnitBodySize, but for items.
* Again it's up to you to figure out a fictional item height.
*/
function GetItemHeight takes item i returns real
return 16.
endfunction
/**
* Unit indexers and missiles ( Only if you don't use a dummy recycling library )
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* It is most likely intended that projectiles don't run through a unit indexing process.
* ToogleUnitIndexer runs:
* • Directly before a dummy is created.
* • Directly after dummy unit creation.
*
* Please return the previous setup of your indexing tool ( enabled, disabled ),
* so Missile can properly reset it to the original state.
*/
private function ToogleUnitIndexer takes boolean enable returns boolean
local boolean prev = true//UnitIndexer.enabled
// set UnitIndexer.enabled = enable
return prev
endfunction
/**
* 4. API
* ¯¯¯¯¯¯
*/
//! novjass ( Disables the compiler until the next endnovjass )
// Custom type Missile for your projectile needs.
struct Missile extends array
// Constants:
// ==========
//
readonly static constant string ORIGIN = "origin"
// • Attach point name for fxs on dummies.
readonly static constant real HIT_BOX = (2./3.)
// • Fictional hit box for homing missiles.
// while 0 would be the toe and 1 the head of a unit.
// Available creators:
// ===================
//
static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns Missile
// • Core creator method.
// • May launches any unit.
// • Units of type Missile_DUMMY_UNIT_ID get recycled in the end.
static method create takes real x, real y, real z, real angleInRadians, real distanceToTravel, real endZ returns Missile
// • Converts arguments to fit into createEx, then calls createEx.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns Missile
// • Converts arguments to fit into createEx, then calls createEx.
// Available destructors:
// ======================
//
return true
// • Core destructor.
// • Returning true in any of the interface methods of the MissileStruct module
// will destroy that instance instantly.
method destroy takes nothing returns nothing
// • Destroys the missile during the next timer callback.
method terminate takes nothing returns nothing
// • Destroys the missile instantly.
// Fields you can set and read directly:
// =====================================
//
unit source
unit target // For homing missiles.
real distance // Distance traveled.
player owner // Pseudo owner for faster onCollide evaluation. The proper dummy owner remains PLAYER_NEUTRAL_PASSIVE.
real speed // Vector lenght for missile movement in plane x / y. ( DOES NOT TAKE THE TIMER TIMEOUT IN ACCOUNT )
real acceleration
real damage
real turn // Set a turn rate for missiles.
integer data // For data transfer set and read data.
boolean recycle // Is automatically set to true, when a Missile reaches it's destination.
boolean wantDestroy // Set wantDestroy to true, to destroy a missile during the next timer callback.
// Neither collision nor collisionZ accept values below zero.
real collision // Collision size in plane x / y.
real collisionZ // Collision size in z - axis. ( deprecated )
// Fields you can only read:
// =========================
//
readonly boolean allocated
readonly unit dummy// The dummy unit of this missile.
// Position members for you needs.
readonly MissilePosition origin// Grants access to readonly members of MissilePosition,
readonly MissilePosition impact// which are "x", "y", "z", "angle", "distance", "slope" and the pitch angle "alpha".
// Furthermore method origin.move(x, y, z) and impact.move(x, y, z).
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly real angle// Current angle in radians.
// Method operators for set and read:
// ==================================
//
method operator model= takes string modelFile returns nothing
method operator model takes nothing returns string
// • Adds an effect handle on a missile dummy to it's "origin".
// • You can read the file path.
// • For multiple effects access "this.dummy" in your struct.
method operator scale= takes real value returns nothing
method operator scale takes nothing returns real
// • Set and read the scaling of the dummy unit.
method operator curve= takes real value returns nothing
method operator curve takes nothing returns real
// • Enables curved movement for your missile. ( Pass in radians, NOT degrees )
// • Do not pass in PI/2.
method operator arc= takes real value returns nothing
method operator arc takes nothing returns real
// • Enables arcing movement for your missile. ( Pass in radians, NOT degrees )
// • Do not pass in PI/2.
// Methods for missile movement:
// =============================
//
method bounce takes nothing returns nothing
// • Moves the MissilePosition "origin" to the current missile coordinates.
// • Resets the distance traveled to 0.
method deflect takes real tx, real ty returns nothing
// • Deflect the missile towards tx, ty. Then calls bounce.
method deflectEx takes real tx, real ty, real tz returns nothing
// • Deflect the missile towards tx, ty, tz. Then calls bounce.
method flightTime2Speed takes real duration returns nothing
// • Converts a fly time to a vector lenght for member "speed".
// • Does not take acceleration into account. ( Disclaimer )
method setMovementSpeed takes real value returns nothing
// • Converts Warcraft III movement speed to a vector lenght for member "speed".
// Methods for missile collision: ( all are hashtable entries )
// ==============================
// By default a widget can only be hit once per missile.
//
method hitWidget takes widget w returns nothing
// • Saves a widget in the memory as hit by this instance.
method hasHitWidget takes widget w returns boolean
// • Returns true, if "w" has been hit by this instance.
method removeHitWidget takes widget w returns nothing
// • Removes a widget from this missile's memory for widget collision. ( Can hit "w" again )
method flushHitWidgets takes nothing returns nothing
// • Flushes a missile's memory for widget collision. ( All widgets can be hit again )
method enableHitAfter takes widget w, real seconds returns nothing
// • Automatically calls removeHitWidget(w) after "seconds" time. ( Can hit "w" again after given time )
// UPDATE 3.0.0 ADDITIONAL FEATURES:
// ============================
method effect.addModel takes string model returns effect
method effect.removeModel takes string model returns nothing
method effect.clearModels takes nothing returns nothing
// • Adds/removes/clears Missile sfx models
method effect.getHandle takes string model returns effect
// • For Missiles with multiple sfx models, allows you to access those individual <effect> handles
// • Useful for example if one wants some <effect> to have a certain offset from the Missile's origin
// Module MissileStruct:
// =====================
//
module MissileLaunch // ( optional )
module MissileStruct
// • Enables the entire missile interface for that struct.
// Interface in structs: ( Must implement module MissileStruct )
// =====================
//
// • Write one, many or all of the static method below into your struct.
// • Missiles launched in this struct will run those methods, when their events fire.
//
// • All of those static methods must return a boolean.
// a) return true --> destroys the missile instance instantly.
// b) return false --> keep on flying.
// Available static method:
// ========================
//
static method onCollide takes Missile missile, unit hit returns boolean
// • Runs for units in collision range of a missile.
static method onDestructable takes Missile missile, destructable hit returns boolean
// • Runs for destructables in collision range of a missile.
static method onItem takes Missile missile, item hit returns boolean
// • Runs for items in collision range of a missile.
static method onTerrain takes Missile missile returns boolean
// • Runs when a missile collides with the terrain. ( Ground and cliffs )
static method onFinish takes Missile missile returns boolean
// • Runs only when a missile reaches it's destination.
// • However does not run, if a Missile is destroyed in another method.
static method onPeriod takes Missile missile returns boolean
// • Runs every Missile_TIMER_TIMEOUT seconds.
static method onRemove takes Missile missile returns boolean
// • Runs when a missile is destroyed.
// • Unlike onFinish, onRemove will runs ALWAYS when a missile is destroyed!!!
//
// For onRemove the returned boolean has a unique meaning:
// • Return true will recycle this missile delayed. ( Only if WRITE_DELAYED_MISSILE_RECYCLING = true )
// • Return false will recycle this missile right away.
static method launch takes Missile toLaunch returns nothing
// • Well ... Launches this Missile.
// • Missile "toLaunch" will behave as declared in the struct. ( static methods from above )
// Misc: ( From the global setup )
// =====
//
// Constants:
// ==========
//
public constant real TIMER_TIMEOUT
public constant player NEUTRAL_PASSIVE
public constant integer DUMMY_UNIT_ID
public constant real MAXIMUM_COLLISION_SIZE
public constant boolean USE_COLLISION_Z_FILTER
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING
public constant boolean USE_DESTRUCTABLE_FILTER
public constant boolean USE_ITEM_FILTER
readonly static constant string ORIGIN
readonly static constant real HIT_BOX
// Functions:
// ==========
//
public function GetLocZ takes real x, real y returns real
function GetUnitBodySize takes unit whichUnit returns real
function GetDestructableHeight takes destructable d returns real
function GetItemHeight takes item i returns real
//========================================================================
// Missile system. Make changes carefully.
//========================================================================
//! endnovjass ( Enables the compiler )
// Hello and welcome to Missile.
// I wrote a guideline for every piece of code inside Missile, so you
// can easily understand how the it gets compiled and evaluated.
//
// Let's go!
private keyword Init
globals
// Core constant handle variables of Missile.
private constant trigger CORE = CreateTrigger()
private constant trigger MOVE = CreateTrigger()
private constant timer TMR = CreateTimer()
private constant location LOC = Location(0., 0.)
private constant rect RECT = Rect(0., 0., 0., 0.)
private constant group GROUP = CreateGroup()
// For starting and stopping the timer.
private integer active = 0
// Arrays for data structure.
private integer array instances
private Missile array missileList
private boolexpr array expression
private triggercondition array condition
private integer array removed
private boolean array destroying
private boolean array recycling
// Internal widget filter functions.
private boolexpr destFilter
private boolexpr itemFilter
private boolexpr unitFilter
private TableArray table
private boolean fixedRotationAxes
endglobals
public function GetLocZ takes real x, real y returns real
call MoveLocation(LOC, x, y)
return GetLocationZ(LOC)
endfunction
/*
* Detects game version - Credits to TriggerHappy
* https://www.hiveworkshop.com/threads/308204/
*/
private function IsRotationAxesFixed takes nothing returns boolean
static if LIBRARY_GameVersion then
return GetPatchLevel() == PATCH_LEVEL_131
else
local image img = CreateImage("ReplaceableTextures\\WorldEditUI\\Editor-Toolbar-MapValidation.blp", 64, 64, 0, 0,0,0,64,64,0, 1)
local string tmp = GetLocalizedString("ERROR_ID_CDKEY_INUSE")
if (GetHandleId(img) == -1) then
return false
endif
call DestroyImage(img)
return not ((JASS_MAX_ARRAY_SIZE <= 8192) or (GetLocalizedString("DOWNLOADING_MAP") == "DOWNLOADING_MAP") or (SubString(tmp, StringLength(tmp)-1, StringLength(tmp)) == ")") or (GetLocalizedString("WINDOW_MODE_WINDOWED") == "WINDOW_MODE_WINDOWED"))
endif
endfunction
/*===============================================================================================*/
/*
* One allocator for the whole library
*/
private struct Node extends array
static if LIBRARY_Alloc then
implement optional Alloc
else
/*
* Credits to MyPad for the allocation algorithm
*/
private static thistype array stack
static method allocate takes nothing returns thistype
local thistype node = stack[0]
if stack[node] == 0 then
static if LIBRARY_ErrorMessage then
debug call ThrowError(node == (JASS_MAX_ARRAY_SIZE - 1), "Missile", "allocate()", "thistype", node, "Overflow")
endif
set node = node + 1
set stack[0] = node
else
set stack[0] = stack[node]
set stack[node] = 0
endif
return node
endmethod
method deallocate takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "Missile", "deallocate()", "thistype", 0, "Null node")
debug call ThrowError(stack[this] > 0, "Missile", "deallocate()", "thistype", this, "Double-free")
endif
set stack[this] = stack[0]
set stack[0] = this
endmethod
endif
endstruct
/*
* For calculating <effect> orientation
*/
private struct EulerAngles extends array
private static constant integer XY_STEPS = 420
private static constant integer Z_STEPS = 270
private static TableArray angleTable
private static real xyDelta = (2.00*bj_PI)/XY_STEPS
private static real zDelta = (2.00*bj_PI)/Z_STEPS
private static integer zSteps = 0
readonly static real yaw = 0.00
readonly static real pitch = 0.00
readonly static real roll = 0.00
static method from takes real xyAngle, real zAngle returns nothing
local integer key
set xyAngle = ModuloReal(xyAngle, 2.00*bj_PI)
set zAngle = ModuloReal(zAngle, 2.00*bj_PI)
set key = R2I(zAngle/zDelta)*XY_STEPS + R2I(xyAngle/xyDelta)
set yaw = angleTable[0].real[key]
set pitch = angleTable[1].real[key]
set roll = angleTable[2].real[key]
endmethod
/*
* Based on Cyclotrutan's algorithm
* https://www.hiveworkshop.com/threads/orienteffect.313410/
*/
private static method initAngles takes integer key, real dx, real dy, real dz returns nothing
local real N = dx*dx + dy*dy
local real norm = N + dz*dz
local real cp
local real pitch
if norm == 0 then
return
endif
set norm = 1.00001*SquareRoot(norm)
set dx = dx/norm
set dy = dy/norm
set dz = dz/norm
if N == 0 then
set angleTable[0].real[key] = 0.00
set angleTable[2].real[key] = 0.00
if dz > 0 then
set angleTable[1].real[key] = -bj_PI*0.5
else
set angleTable[1].real[key] = bj_PI*0.5
endif
return
endif
set N = SquareRoot(N)
if dy < 0 then
set pitch = Asin(dx*dz/N) + bj_PI
set cp = Cos(pitch)
if fixedRotationAxes then
set angleTable[0].real[key] = Acos(dx/cp)
set angleTable[2].real[key] = -Asin(dy*dz/(N*cp)) + bj_PI
else
set angleTable[0].real[key] = -Asin(dy*dz/(N*cp)) + bj_PI
set angleTable[2].real[key] = Acos(dx/cp)
endif
else
set pitch = -Asin(dx*dz/N)
set cp = Cos(pitch)
if fixedRotationAxes then
set angleTable[0].real[key] = Acos(dx/cp)
set angleTable[2].real[key] = Asin(dy*dz/(N*cp))
else
set angleTable[0].real[key] = Asin(dy*dz/(N*cp))
set angleTable[2].real[key] = Acos(dx/cp)
endif
endif
set angleTable[1].real[key] = pitch
endmethod
private static method initOrientationTables takes integer zStep returns nothing
local real xy = Cos(zStep*zDelta)
local real z = Sin(zStep*zDelta)
local integer xySteps = 0
loop
exitwhen xySteps == XY_STEPS
call initAngles(zStep*XY_STEPS + xySteps, xy*Cos(xySteps*xyDelta), xy*Sin(xySteps*xyDelta), z)
set xySteps = xySteps + 1
endloop
endmethod
private static method initTables takes nothing returns nothing
call initOrientationTables(zSteps)
call initOrientationTables(zSteps + Z_STEPS/2)
endmethod
static method onScopeInit takes nothing returns nothing
set angleTable = TableArray[3]
set fixedRotationAxes = IsRotationAxesFixed()
loop
exitwhen zSteps == Z_STEPS/2
call ForForce(bj_FORCE_PLAYER[0], function thistype.initTables)
set zSteps = zSteps + 1
endloop
endmethod
endstruct
/*
* List of <effect>s
*/
private struct EffectHandle extends array
effect effect
private static method onRemove takes thistype node returns nothing
call DestroyEffect(node.effect)
set node.effect = null
call Node(node).deallocate()
endmethod
private static method allocate takes nothing returns thistype
return Node.allocate()
endmethod
private method deallocate takes nothing returns nothing
call Node(this).deallocate()
endmethod
implement InstantiatedList
endstruct
/*
* Effect is a cluster of <effect>s that can easily be controlled as a single object
*/
private struct Effect extends array
private static TableArray lookupTable
private method operator handle takes nothing returns EffectHandle
return this
endmethod
method operator missile takes nothing returns Missile
return this
endmethod
method addModel takes string model returns effect
local EffectHandle node = Node.allocate()
set lookupTable[this][StringHash(model)] = node
set node.effect = AddSpecialEffect(model, this.missile.x, this.missile.y)
call BlzSetSpecialEffectZ(node.effect, this.missile.z)
call BlzSetSpecialEffectScale(node.effect, this.missile.scale)
call this.handle.pushBack(node)
return node.effect
endmethod
method removeModel takes string model returns nothing
local integer stringId = StringHash(model)
call EffectHandle.remove(lookupTable[this][stringId])
call lookupTable[this].remove(stringId)
endmethod
method clearModels takes nothing returns nothing
call this.handle.flush()
call lookupTable[this].flush()
endmethod
method getHandle takes string model returns effect
return EffectHandle(lookupTable[this][StringHash(model)]).effect
endmethod
method scale takes real value returns nothing
local EffectHandle node = this.handle.next
loop
exitwhen node == this.handle
call BlzSetSpecialEffectScale(node.effect, value)
set node = node.next
endloop
endmethod
method move takes real x, real y, real z returns nothing
local EffectHandle node = this.handle.next
local real dx = x - this.missile.x
local real dy = y - this.missile.y
local real dz = z - this.missile.z
static if not LIBRARY_BoundSentinel and not LIBRARY_WorldBounds then
if not RectContainsCoords(bj_mapInitialPlayableArea, x, y) then
return
endif
elseif LIBRARY_WorldBounds then
if not (x < WorldBounds.maxX and x > WorldBounds.minX and y < WorldBounds.maxY and y > WorldBounds.minY) then
return
endif
endif
loop
exitwhen node == this.handle
call BlzSetSpecialEffectPosition(node.effect, BlzGetLocalSpecialEffectX(node.effect) + dx, BlzGetLocalSpecialEffectY(node.effect) + dy, BlzGetLocalSpecialEffectZ(node.effect) + dz)
set node = node.next
endloop
endmethod
method orient takes real xyAngle, real pitch returns nothing
local EffectHandle node = this.handle.next
call EulerAngles.from(xyAngle, pitch)
loop
exitwhen node == this.handle
call BlzSetSpecialEffectOrientation(node.effect, EulerAngles.yaw, EulerAngles.pitch, EulerAngles.roll)
set node = node.next
endloop
endmethod
static method create takes nothing returns thistype
return EffectHandle.create()
endmethod
method destroy takes nothing returns nothing
call lookupTable[this].flush()
call this.handle.destroy()
endmethod
static method onScopeInit takes nothing returns nothing
set lookupTable = TableArray[JASS_MAX_ARRAY_SIZE]
endmethod
endstruct
// Simple trigonometry.
struct MissilePosition extends array
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real square
readonly real slope
readonly real alpha
// Creates an origin - impact link.
private thistype ref
private static method math takes thistype a, thistype b returns nothing
local real dx
local real dy
loop
set dx = b.x - a.x
set dy = b.y - a.y
set dx = dx*dx + dy*dy
set dy = SquareRoot(dx)
exitwhen dx != 0. and dy != 0.
set b.x = b.x + .01
set b.z = b.z - Missile_GetLocZ(b.x -.01, b.y) + Missile_GetLocZ(b.x, b.y)
endloop
set a.square = dx
set a.distance = dy
set a.angle = Atan2(b.y - a.y, b.x - a.x)
set a.slope = (b.z - a.z)/dy
set a.alpha = Atan(a.slope)*bj_RADTODEG
// Set b.
if b.ref == a then
set b.angle = a.angle + bj_PI
set b.distance = dy
set b.slope = -a.slope
set b.alpha = -a.alpha
set b.square = dx
endif
endmethod
static method link takes thistype a, thistype b returns nothing
set a.ref = b
set b.ref = a
call math(a, b)
endmethod
method move takes real toX, real toY, real toZ returns nothing
set x = toX
set y = toY
set z = toZ + Missile_GetLocZ(toX, toY)
if ref != this then
call math(this, ref)
endif
endmethod
static method create takes real x, real y, real z returns MissilePosition
local thistype this = Node.allocate()
set ref = this
call move(x, y, z)
return this
endmethod
method destroy takes nothing returns nothing
call Node(this).deallocate()
endmethod
endstruct
struct MissileList extends array
method operator missile takes nothing returns Missile
return this
endmethod
implement StaticList
endstruct
struct Missile extends array
// Attach point name for effects created by model=.
readonly static constant string ORIGIN = "origin"
// Set a ficitional hit box in range of 0 to 1,
// while 0 is the toe and 1 the head of a unit.
readonly static constant real HIT_BOX = (2./3.)
// Checks for double launching. Throws an error message.
debug boolean launched
// The position of a missile using curve= does not
// match the position used by Missile's trigonometry.
// Therefore we need this member two times.
// Readable x / y / z for your needs and posX / posY for cool mathematics.
private real posX
private real posY
private real dist// distance
// Readonly members:
// =================
//
// Prevents a double free case.
readonly boolean allocated
// The dummy unit.
readonly unit dummy
// Position members for your needs.
readonly MissilePosition origin// Grants access to readonly members of MissilePosition,
readonly MissilePosition impact// which are "x", "y", "z", "angle", "distance", "slope" and "alpha".
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly real angle// Current angle
readonly real prevX
readonly real prevY
readonly real prevZ
// Collision detection type. ( Evaluated new in each loop )
readonly integer collisionType// Current collision type ( circular or rectangle )
unit source
unit target // For homing missiles.
real distance // Distance traveled.
player owner // Pseudo owner for faster onCollide evaluation. The proper dummy owner is PLAYER_NEUTRAL_PASSIVE.
real speed // Vector lenght for missile movement in plane x / y.
real acceleration
real damage
integer data // For data transfer set and read data.
boolean recycle // Is set to true, when a Missile reaches it's destination.
real turn // Sets a turn rate for a missile.
real collision // Collision size in plane x / y.
private static method onInsert takes thistype node returns nothing
call MissileList.pushBack(node)
endmethod
private static method onRemove takes thistype node returns nothing
call MissileList.remove(node)
endmethod
implement List
static method p_makeHead takes thistype node returns nothing
set node.prev = node
set node.next = node
endmethod
method operator effect takes nothing returns Effect
return this
endmethod
// Setting collision z is deprecated since Missile v2.5.
method operator collisionZ= takes real value returns nothing
endmethod
method operator collisionZ takes nothing returns real
return collision
endmethod
method operator sfx takes nothing returns effect
return null
endmethod
private string path
method operator model= takes string file returns nothing
call effect.clearModels()
// null and ""
if StringLength(file) > 0 then
call effect.addModel(file)
set path = file
else
set path = null
endif
endmethod
method operator model takes nothing returns string
return path
endmethod
real open
// Enables curved movement for your missile.
// Remember that the result of Tan(PI/2) is infinity.
method operator curve= takes real value returns nothing
set open = Tan(value)*origin.distance
endmethod
method operator curve takes nothing returns real
return Atan(open/origin.distance)
endmethod
private real height
// Enables arcing movement for your missile.
method operator arc= takes real value returns nothing
set height = Tan(value)*origin.distance/4
endmethod
method operator arc takes nothing returns real
return Atan(4*height/origin.distance)
endmethod
private real scaling
method operator scale= takes real value returns nothing
call SetUnitScale(dummy, value, 0., 0.)
call effect.scale(value)
set scaling = value
endmethod
method operator scale takes nothing returns real
return scaling
endmethod
method bounce takes nothing returns nothing
call origin.move(x, y, z)
set dist = 0.
endmethod
method deflect takes real tx, real ty returns nothing
local real a = 2.*Atan2(ty - y, tx - x) + bj_PI - angle
call impact.move(x + (origin.distance - dist)*Cos(a), y + (origin.distance - dist)*Sin(a), impact.z)
call bounce()
endmethod
method deflectEx takes real tx, real ty, real tz returns nothing
call impact.move(impact.x, impact.y, tz)
call deflect(tx, ty)
endmethod
method flightTime2Speed takes real duration returns nothing
set speed = RMaxBJ(0.00000001, (origin.distance - dist)*Missile_TIMER_TIMEOUT/RMaxBJ(0.00000001, duration))
endmethod
method setMovementSpeed takes real value returns nothing
set speed = value*Missile_TIMER_TIMEOUT
endmethod
boolean wantDestroy// For "true" a missile is destroyed on the next timer callback. 100% safe.
method destroy takes nothing returns nothing
set wantDestroy = true
endmethod
// Instantly destroys a missile.
method terminate takes nothing returns nothing
if allocated then
set allocated = false
call impact.destroy()
call origin.destroy()
call table[this].flush()
set source = null
set target = null
set dummy = null
set owner = null
call this.effect.destroy()
call remove(this)
endif
endmethod
// Runs in createEx.
private method resetMembers takes nothing returns nothing
set path = null
set speed = 0.
set acceleration = 0.
set distance = 0.
set dist = 0.
set height = 0.
set turn = 0.
set open = 0.
set collision = 0.
set collisionType = 0
set stackSize = 0
set scaling = 1.
set wantDestroy = false
set recycle = false
endmethod
// Launches a dummy of your choice.
static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns thistype
local real originX = GetUnitX(missileDummy)
local real originY = GetUnitY(missileDummy)
local real originZ = GetUnitFlyHeight(missileDummy)
local thistype this = Effect.create()
call resetMembers()
set origin = MissilePosition.create(originX, originY, originZ)
set impact = MissilePosition.create(impactX, impactY, impactZ)
call MissilePosition.link(origin, impact)
set posX = originX
set posY = originY
set x = originX
set y = originY
set z = originZ
set angle = origin.angle
set dummy = missileDummy
set allocated = true
if UnitAddAbility(missileDummy, 'Arav') then
call UnitRemoveAbility(missileDummy, 'Arav')
endif
call SetUnitFlyHeight(missileDummy, originZ, 0.)
set table[this].unit[GetHandleId(missileDummy)] = missileDummy
static if LIBRARY_ErrorMessage then
debug call ThrowWarning(GetUnitTypeId(missileDummy) == 0, "Missile", "createEx", "missileDummy", this, "Invalid missile dummy unit ( null )!")
endif
debug set launched = false
return this
endmethod
// Wrapper to createEx.
static method create takes real x, real y, real z, real angle, real distance, real impactZ returns thistype
local real impactX = x + distance*Cos(angle)
local real impactY = y + distance*Sin(angle)
local thistype this = Effect.create()
call resetMembers()
set origin = MissilePosition.create(x, y, z)
set impact = MissilePosition.create(impactX, impactY, impactZ)
call MissilePosition.link(origin, impact)
set .posX = x
set .posY = y
set .x = x
set .y = y
set .z = z
set angle = origin.angle
set dummy = null
set allocated = true
debug set launched = false
return this
endmethod
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns thistype
local real dx = impactX - x
local real dy = impactY - y
local real dz = impactZ - z
return create(x, y, z, Atan2(dy, dx), SquareRoot(dx*dx + dy*dy), impactZ)
endmethod
// Missile motion takes place every Missile_TIMER_TIMEOUT
// before accessing each active struct.
static Missile temp = 0
static method move takes nothing returns boolean
local integer loops = 0 // Current iteration.
local integer limit = 150 // Set iteration border per trigger evaluation to avoid hitting the operation limit.
local thistype this = thistype.temp
local MissilePosition p
local real a
local real d
local unit u
local real newX
local real newY
local real newZ
local real vel
local real point
loop
exitwhen this == MissileList.head or loops == limit
set p = origin
// Save previous, respectively current missile position.
set prevX = x
set prevY = y
set prevZ = z
// Evaluate the collision type.
set vel = speed
set speed = vel + acceleration
if vel < collision*Missile_COLLISION_ACCURACY_FACTOR then
set collisionType = Missile_COLLISION_TYPE_CIRCLE
else
set collisionType = Missile_COLLISION_TYPE_RECTANGLE
endif
// Update missile guidance to its intended target.
set u = target
if u != null then
if 0 == GetUnitTypeId(u) then
set target = null
else
call origin.move(x, y, z)
call impact.move(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + GetUnitBodySize(u)*Missile.HIT_BOX)
set dist = 0
set height = 0
set curve = 0
endif
endif
set a = p.angle
// Update the missile facing angle depending on the turn ratio.
if 0. != turn and Cos(angle - a) < Cos(turn) then
if 0. > Sin(a - angle) then
set angle = angle - turn
else
set angle = angle + turn
endif
else
set angle = a
endif
// Update the missile position on the parabola.
set d = p.distance// origin - impact distance.
set recycle = dist + vel >= d
if recycle then
set point = d
set distance = distance + d - dist
else
set distance = distance + vel
set point = dist + vel
endif
set dist = point
set newX = posX + vel*Cos(angle)
set newY = posY + vel*Sin(angle)
set posX = newX
set posY = newY
// Update point(x/y) if a curving trajectory is defined.
set u = dummy
if 0. != open and target == null then
set vel = 4*open*point*(d - point)/p.square
set a = angle + bj_PI/2
set newX = newX + vel*Cos(a)
set newY = newY + vel*Sin(a)
set a = angle + Atan(-((4*open)*(2*point - d))/p.square)
else
set a = angle
endif
// Update pos z if an arc or height is set.
call MoveLocation(LOC, newX, newY)
set terrainZ = GetLocationZ(LOC)
if 0. == height and 0. == p.alpha then
set newZ = p.z - terrainZ
else
set newZ = p.z - terrainZ + p.slope*point
if 0. != height and target == null then
set newZ = newZ + (4*height*point*(d - point)/p.square)
endif
endif
// Set missile position and orientation
call .effect.move(newX, newY, newZ)
call .effect.orient(a, Atan2(newZ - prevZ, vel))
if u != null then
call SetUnitFlyHeight(u, newZ, 0.)
call SetUnitFacing(u, a*bj_RADTODEG)
call SetUnitLookAt(u, "bone_head", u, 2.*newX - prevX, 2.*newY - prevY, 2.*newZ - prevZ)
// WorldBounds > BoundSentinel.
static if not LIBRARY_BoundSentinel and not LIBRARY_WorldBounds then
if RectContainsCoords(bj_mapInitialPlayableArea, newX, newY) then
call SetUnitX(u, newX)
call SetUnitY(u, newY)
endif
elseif LIBRARY_WorldBounds then
if newX < WorldBounds.maxX and newX > WorldBounds.minX and newY < WorldBounds.maxY and newY > WorldBounds.minY then
call SetUnitX(u, newX)
call SetUnitY(u, newY)
endif
else
call SetUnitX(u, newX)
call SetUnitY(u, newY)
endif
endif
set .x = newX
set .y = newY
set .z = newZ
set loops = loops + 1
set this = MissileList(this).next
endloop
set u = null
set thistype.temp = this
return this == MissileList.head
endmethod
// Widget collision API:
// =====================
//
// Runs automatically on widget collision.
method hitWidget takes widget w returns nothing
if w != null then
set table[this].widget[GetHandleId(w)] = w
endif
endmethod
// All widget which have been hit return true.
method hasHitWidget takes widget w returns boolean
return table[this].handle.has(GetHandleId(w))
endmethod
// Removes a widget from the missile's memory of hit widgets. ( This widget can be hit again )
method removeHitWidget takes widget w returns nothing
if w != null then
call table[this].handle.remove(GetHandleId(w))
endif
endmethod
// Flushes a missile's memory for collision. ( All widgets can be hit again )
method flushHitWidgets takes nothing returns nothing
call table[this].flush()
call hitWidget(dummy)
endmethod
// Tells missile to call removeHitWidget(w) after "seconds" time.
// Does not apply to widgets, which are already hit by this missile.
readonly integer stackSize
method enableHitAfter takes widget w, real seconds returns nothing
local integer id = GetHandleId(w)
local Table t
if w != null then
set t = table[this]
if not t.has(id) then
set t[id] = stackSize
set t[stackSize] = id
set stackSize = stackSize + 1
endif
set t.real[id] = seconds
endif
endmethod
method updateStack takes nothing returns nothing
local integer dex = 0
local integer id
local real time
local Table t
loop
exitwhen dex == stackSize
set t = table[this]
set id = t[dex]
set time = t.real[id] - Missile_TIMER_TIMEOUT
if time <= 0. or not t.handle.has(id) then
set stackSize = stackSize - 1
set id = t[stackSize]
set t[dex] = id
set t[id] = dex
// Enables hit.
call t.handle.remove(id)
// Remove data from stack.
call t.real.remove(id)
call t.remove(id)
call t.remove(stackSize)
else
set t.real[id] = time
set dex = dex + 1
endif
endloop
endmethod
// Widget collision code:
// ======================
//
private static boolean circle = true
//
// 1.) Rectangle collision for fast moving missiles with small collision radius.
//
// Runs for destructables and items in a rectangle.
// Checks if widget w is in collision range of a missile.
// Is not precise in z - axis collision.
private method isWidgetInRectangle takes widget w, real wz, real distance returns boolean
local real wx = GetWidgetX(w)
local real wy = GetWidgetY(w)
local real dz = Missile_GetLocZ(wx, wy) - terrainZ
local real dx = x - prevX
local real dy = y - prevY
local real s = (dx*(wx - prevX) + dy*(wy - prevY))/(dx*dx + dy*dy)
if s < 0. then
set s = 0.
elseif s > 1 then
set s = 1.
endif
set dx = (prevX + s*dx) - wx
set dy = (prevY + s*dy) - wy
return dx*dx + dy*dy <= distance*distance and dz + wz >= z - distance and dz <= z + distance
endmethod
//
// 2.) Circular collision detection for all other missiles.
//
// Returns true for widgets in a xyz collision range.
private method isWidgetInRange takes widget w, real wz, real distance returns boolean
local real wx = GetWidgetX(w)
local real wy = GetWidgetY(w)
local real dz = Missile_GetLocZ(wx, wy) - terrainZ
// collision in plane x and y, collision in z axis.
return IsUnitInRangeXY(dummy, wx, wy, distance) and dz + wz >= z - distance and dz <= z + distance
endmethod
//
// 3.) Action functions inside the widget enumeration thread.
//
// Runs for every enumerated destructable.
// • Directly filters out already hit destructables.
// • Distance formula based on the Pythagorean theorem.
//
private static method filterDests takes nothing returns boolean
local destructable d = GetFilterDestructable()
local boolean b = false
if not table[temp].handle.has(GetHandleId(d)) then
if circle then
set b = temp.isWidgetInRange(d, GetDestructableHeight(d), temp.collision)
else
set b = temp.isWidgetInRectangle(d, GetDestructableHeight(d), temp.collision)
endif
endif
set d = null
return b
endmethod
//
// Runs for every enumerated item.
// • Directly filters out already hit items.
// • Distance formula based on the Pythagorean theorem.
// • Items have a fix collision size of 16.
//
private static method filterItems takes nothing returns boolean
local item i = GetFilterItem()
local boolean b = false
if not table[temp].handle.has(GetHandleId(i)) then
if circle then // Item in missile collision size or item pathing in missile range.
set b = temp.isWidgetInRange(i, GetItemHeight(i), RMaxBJ(temp.collision, 16.))
else
set b = temp.isWidgetInRectangle(i, GetItemHeight(i), RMaxBJ(temp.collision, 16.))
endif
endif
set i = null
return b
endmethod
//
// 4.) Filter function for rectangle unit collision.
//
// Runs for every enumerated units.
// • Filters out units which are not in collision range in plane x / y.
// • Inlined and therefore a bit faster than item and destructable collision.
//
private static method filterUnits takes nothing returns boolean
local thistype this = thistype.temp
local unit u = GetFilterUnit()
local real dx
local real dy
local real s
local boolean is = false
if not table[this].handle.has(GetHandleId(u)) then
set dx = x - prevX
set dy = y - prevY
set s = (dx*(GetUnitX(u) - prevX) + dy*(GetUnitY(u)- prevY))/(dx*dx + dy*dy)
if s < 0. then
set s = 0.
elseif s > 1. then
set s = 1.
endif
set is = IsUnitInRangeXY(u, prevX + s*dx, prevY + s*dy, collision)
endif
set u = null
return is
endmethod
//
// 5.) Proper rect preparation.
//
// For rectangle.
private method prepareRectRectangle takes nothing returns nothing
local real x1 = prevX
local real y1 = prevY
local real x2 = x
local real y2 = y
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
// What is min, what is max ...
if x1 < x2 then
if y1 < y2 then
call SetRect(RECT, x1 - d, y1 - d, x2 + d, y2 + d)
else
call SetRect(RECT, x1 - d, y2 - d, x2 + d, y1 + d)
endif
elseif y1 < y2 then
call SetRect(RECT, x2 - d, y1 - d, x1 + d, y2 + d)
else
call SetRect(RECT, x2 - d, y2 - d, x1 + d, y1 + d)
endif
endmethod
//
// For circular.
private method prepareRectCircle takes nothing returns nothing
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
call SetRect(RECT, x - d, y - d, x + d, y + d)
endmethod
//
// 5.) API for the MissileStruct iteration.
//
method groupEnumUnitsRectangle takes nothing returns nothing
call prepareRectRectangle()
set thistype.temp = this
call GroupEnumUnitsInRect(GROUP, RECT, unitFilter)
endmethod
//
// Prepares destructable enumeration, then runs enumDests.
method checkDestCollision takes code func returns nothing
set circle = collisionType == Missile_COLLISION_TYPE_CIRCLE
if circle then
call prepareRectCircle()
else
call prepareRectRectangle()
endif
set thistype.temp = this
call EnumDestructablesInRect(RECT, destFilter, func)
endmethod
//
// Prepares item enumeration, then runs enumItems.
method checkItemCollision takes code func returns nothing
set circle = collisionType == Missile_COLLISION_TYPE_CIRCLE
if circle then
call prepareRectCircle()
else
call prepareRectRectangle()
endif
set thistype.temp = this
call EnumItemsInRect(RECT, itemFilter, func)
endmethod
static if Missile_WRITE_DELAYED_MISSILE_RECYCLING then
method nullBefore takes nothing returns nothing
endmethod
endif
// Does not check for 'Aloc' and 'Amrf' as they could be customized.
private static method onScopeInit takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((Missile_MAXIMUM_COLLISION_SIZE < 0), "Missile", "DEBUG_MISSILE", "collision", 0, "Global setup for public real MAXIMUM_COLLISION_SIZE is incorrect, below zero! This map currently can't use Missile!")
endif
set unitFilter = Filter(function thistype.filterUnits)
set destFilter = Filter(function thistype.filterDests)
set itemFilter = Filter(function thistype.filterItems)
call TriggerAddCondition(MOVE, Condition(function thistype.move))
set table = TableArray[JASS_MAX_ARRAY_SIZE]
endmethod
implement Init
endstruct
// Boolean expressions per struct:
// ===============================
//
// Condition function for the core trigger evaluation. ( Runs for all struct using module MissileStruct )
private function MissileCreateExpression takes integer structId, code func returns nothing
set expression[structId] = Condition(func)
endfunction
// Creates a collection for a struct. ( Runs for all struct using module MissileStruct )
private function MissileCreateCollection takes integer structId returns nothing
local Missile node = Node.allocate()
call Missile.p_makeHead(node) // Make a circular list
set missileList[structId] = node
endfunction
// Core:
// =====
//
// Fires every Missile_TIMER_TIMEOUT.
private function Fire takes nothing returns nothing
local integer i = removed[0]
set removed[0] = 0
loop
exitwhen 0 == i
if recycling[i] then
call TriggerRemoveCondition(CORE, condition[i])
set condition[i] = null
set active = active - 1
endif
set destroying[i] = false
set recycling[i] = false
set i = removed[i]
endloop
if 0 == active then
call PauseTimer(TMR)
else
// Move all launched missiles.
//set Missile.temp = nextNode[0]
set Missile.temp = MissileList.front
set i = 0
loop
exitwhen TriggerEvaluate(MOVE)
exitwhen i == 60// Moved over 8910 missiles, something buggy happened.
set i = i + 1 // This case is impossible, hence never happens. But if I'm wrong, which also never happens
endloop // then the map 100% crashes. i == 66 will prevent the game crash and Missile will start to
// to debug itself over the next iterations.
// Access all structs using module MissileStruct.
static if DEBUG_MODE and LIBRARY_ErrorMesssage then
if not TriggerEvaluate(CORE) then
call ThrowWarning(true, "Missile", "Fire", "op limit", 0, /*
*/"You just ran into a op limit!
The op limit protection of Missile is insufficient for your map.
The code of the missile module can't run in one thread and must be splitted.
If unsure make a post in the official 'The Hive Workshop' forum thread of Missile!")
endif
else
call TriggerEvaluate(CORE)
endif
endif
endfunction
// Conditionally starts the timer.
private function StartPeriodic takes integer structId returns nothing
if 0 == instances[structId] or destroying[structId] then
if destroying[structId] then
set recycling[structId] = false
else
if 0 == active then
call TimerStart(TMR, Missile_TIMER_TIMEOUT, true, function Fire)
endif
set active = active + 1
set condition[structId] = TriggerAddCondition(CORE, expression[structId])
endif
endif
set instances[structId] = instances[structId] + 1
endfunction
// Conditionally stops the timer in the next callback.
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if 0 == instances[structId] and condition[structId] != null then
if not destroying[structId] and not recycling[structId] then
set destroying[structId] = true
set recycling[structId] = true
set removed[structId] = removed[0]
set removed[0] = structId
endif
endif
endfunction
// Modules:
// ========
//
// You want to place module MissileLaunch at the very top of your struct.
module MissileLaunch
static method launch takes Missile missile returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(missile.launched, "thistype", "launch", "missile.launched", missile, "This missile was already launched before!")
endif
debug set missile.launched = true
call missileList[thistype.typeid].pushBack(missile)
call StartPeriodic(thistype.typeid)
endmethod
endmodule
module MissileTerminate
// Called from missileIterate. "P" to avoid code collision.
static method missileTerminateP takes Missile node returns nothing
if node.allocated then
static if thistype.onRemove.exists then
call thistype.onRemove(node)
endif
call node.terminate()
call StopPeriodic(thistype.typeid)
endif
endmethod
endmodule
// Allows you to inject missile in certain stages of the motion process.
module MissileAction
static if DEBUG_MODE then
// Runs check during compile time.
static if thistype.onMissile.exists then
Error Message from library Missile in struct thistype !
thistype.onMissile is a reserved name for Missile, once you implemented MissileStruct.
thistype.onMissile is currently not supported by library Missile.
Please delete or re-name that method.
endif
endif
static if thistype.onItem.exists then
private static method missileActionItem takes nothing returns nothing
local item i = GetEnumItem()
local Missile this = Missile.temp
if this.allocated then
set table[this].item[GetHandleId(i)] = i
if thistype.onItem(this, i) then
call missileTerminateP(this)
endif
endif
set i = null
endmethod
endif
static if thistype.onDestructable.exists then
private static method missileActionDest takes nothing returns nothing
local destructable d = GetEnumDestructable()
local Missile this = Missile.temp
if this.allocated then
set table[this].destructable[GetHandleId(d)] = d
if thistype.onDestructable(this, d) then
call missileTerminateP(this)
endif
endif
set d = null
endmethod
endif
// Runs every Missile_TIMER_TIMEOUT for this struct.
static method missileIterateP takes nothing returns boolean
local Missile this = missileList[thistype.typeid].front
local Missile node
local real collideZ
local boolean b
local unit u
loop
exitwhen this == missileList[thistype.typeid]
set node = this.next// The linked list should not lose the next node.
if this.wantDestroy then
call thistype.missileTerminateP(this)
else
if this.stackSize > 0 then
call this.updateStack()
endif
// Runs unit collision.
static if thistype.onCollide.exists then
if this.allocated and 0. < this.collision then
set b = this.collisionType == Missile_COLLISION_TYPE_RECTANGLE
if b then
call this.groupEnumUnitsRectangle()
else
call GroupEnumUnitsInRange(GROUP, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, null)
endif
loop
set u = FirstOfGroup(GROUP)
exitwhen u == null
call GroupRemoveUnit(GROUP, u)
if not table[this].handle.has(GetHandleId(u)) then
if b or (this.dummy == null and IsUnitInRangeXY(u, this.x, this.y, this.collision)) or IsUnitInRange(u, this.dummy, this.collision) then
// Eventually run z collision checks.
static if Missile_USE_COLLISION_Z_FILTER then
set collideZ = Missile_GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - this.terrainZ
if (collideZ + GetUnitBodySize(u) >= this.z - this.collisionZ) and (collideZ <= this.z + this.collisionZ) then
// Mark as hit.
set table[this].unit[GetHandleId(u)] = u
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
endif
else
// Runs unit collision without z collision checks.
set table[this].unit[GetHandleId(u)] = u
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
endif
endif
endif
endloop
endif
endif
// Runs destructable collision.
static if thistype.onDestructable.exists then
// Check if the missile is not terminated.
if this.allocated and 0. < this.collision then
call this.checkDestCollision(function thistype.missileActionDest)
endif
endif
// Runs item collision.
static if thistype.onItem.exists then
// Check if the missile is not terminated.
if this.allocated and 0. < this.collision then
call this.checkItemCollision(function thistype.missileActionItem)
endif
endif
// Runs when the destination is reached.
if this.recycle and this.allocated then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
endif
else
call thistype.missileTerminateP(this)
endif
endif
// Runs on terrian collision.
static if thistype.onTerrain.exists then
if this.allocated and 0. > this.z and thistype.onTerrain(this) then
call missileTerminateP(this)
endif
endif
// Runs every Missile_TIMER_TIMEOUT.
static if thistype.onPeriod.exists then
if this.allocated and thistype.onPeriod(this) then
call missileTerminateP(this)
endif
endif
endif
set this = node
endloop
set u = null
static if DEBUG_MODE and LIBRARY_ErrorMessage then
return true
else
return false
endif
endmethod
endmodule
module MissileStruct
implement MissileLaunch
implement MissileTerminate
implement MissileAction
private static method onInit takes nothing returns nothing
call MissileCreateCollection(thistype.typeid)
call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)
endmethod
endmodule
private module Init
private static method onInit takes nothing returns nothing
call EulerAngles.onScopeInit()
call Effect.onScopeInit()
call Missile.onScopeInit()
endmethod
endmodule
// The end!
endlibrary
library AnimationManager requires Table, TimerUtils
/*
Internal notes: Only 1 AnimationManager can be active
for a unit at a given time.
AnimationManager automatically stops when a unit is
dead. This can be changed in the globals section
*/
globals
private constant boolean ANIM_DEATH_PERSIST = false
endglobals
native UnitAlive takes unit id returns boolean
private module AnimationManagerInit
private static thistype current = 0
private static unit currentUnit = null
private static TableArray table = 0
private static constant integer ANIM_TYPE_MAP = 0
private static constant integer ANIM_STRING = 1
private static constant integer ANIM_INDEX = 2
private static constant integer ANIM_DURATION = 3
private static constant integer ANIM_TIMER = 4
private static constant integer ANIM_UNIT = 5
private static constant integer ANIM_TYPE_STRING = 0
private static constant integer ANIM_TYPE_INDEX = 1
static method operator [] takes unit whichunit returns thistype
if currentUnit != whichunit then
set current = GetHandleId(whichunit)
set currentUnit = whichunit
endif
return current
endmethod
private static method setAnimByIndex takes unit whichunit, integer index, boolean queueStand returns nothing
call SetUnitAnimationByIndex(whichunit, index)
if queueStand then
call QueueUnitAnimation(whichunit, "stand")
endif
endmethod
private static method setAnim takes unit whichunit, string anim, boolean queueStand returns nothing
call SetUnitAnimation(whichunit, anim)
if queueStand then
call QueueUnitAnimation(whichunit, "stand")
endif
endmethod
private method setAnimLoopCore takes nothing returns nothing
if not table[ANIM_UNIT].unit.has(this) then
set table[ANIM_UNIT].unit[this] = currentUnit
endif
if not table[ANIM_TIMER].timer.has(this) then
set table[ANIM_TIMER].timer[this] = NewTimerEx(this)
else
call PauseTimer(table[ANIM_TIMER].timer[this])
endif
endmethod
private method release takes nothing returns nothing
if table[ANIM_TIMER].timer.has(this) then
call PauseTimer(table[ANIM_TIMER].timer[this])
call ReleaseTimer(table[ANIM_TIMER].timer[this])
if UnitAlive(table[ANIM_UNIT].unit[this]) then
call setAnim(table[ANIM_UNIT].unit[this], "stand", true)
endif
endif
call table[ANIM_UNIT].unit.remove(this)
call table[ANIM_TIMER].timer.remove(this)
call table[ANIM_DURATION].real.remove(this)
call table[ANIM_INDEX].integer.remove(this)
call table[ANIM_STRING].string.remove(this)
call table[ANIM_TYPE_MAP].integer.remove(this)
endmethod
private static method onAnimStringLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if GetUnitTypeId(table[ANIM_UNIT].unit[this]) == 0 then
call this.release()
return
endif
static if not ANIM_DEATH_PERSIST then
if not UnitAlive(table[ANIM_UNIT].unit[this]) then
call this.release()
return
endif
endif
call setAnim(table[ANIM_UNIT].unit[this], table[ANIM_STRING].string[this], false)
endmethod
private static method onAnimIndexLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if GetUnitTypeId(table[ANIM_UNIT].unit[this]) == 0 then
call this.release()
return
endif
static if not ANIM_DEATH_PERSIST then
if not UnitAlive(table[ANIM_UNIT].unit[this]) then
call this.release()
return
endif
endif
call setAnimByIndex(table[ANIM_UNIT].unit[this], table[ANIM_INDEX].integer[this], false)
endmethod
method setAnimLoop takes string anim, real interval returns nothing
// setting anim to null or "" will release the timer handle.
if anim == null or anim == "" then
call this.release()
return
endif
call this.setAnimLoopCore()
if table[ANIM_TYPE_MAP].integer[this] == ANIM_TYPE_INDEX then
call table[ANIM_INDEX].integer.remove(this)
endif
set table[ANIM_TYPE_MAP].integer[this] = ANIM_TYPE_STRING
set table[ANIM_STRING].string[this] = anim
set table[ANIM_DURATION].real[this] = interval
call setAnim(table[ANIM_UNIT].unit[this], anim, false)
call TimerStart(table[ANIM_TIMER].timer[this], interval, true, function thistype.onAnimStringLoop)
endmethod
method setAnimLoopByIndex takes integer index, real interval returns nothing
// setting anim to null or "" will release the timer handle.
if index < 0 then
call this.release()
return
endif
call this.setAnimLoopCore()
if table[ANIM_TYPE_MAP].integer[this] == ANIM_TYPE_STRING then
call table[ANIM_STRING].string.remove(this)
endif
set table[ANIM_TYPE_MAP].integer[this] = ANIM_TYPE_STRING
set table[ANIM_INDEX].integer[this] = index
set table[ANIM_DURATION].real[this] = interval
call setAnimByIndex(table[ANIM_UNIT].unit[this], index, false)
call TimerStart(table[ANIM_TIMER].timer[this], interval, true, function thistype.onAnimIndexLoop)
endmethod
private static method onInit takes nothing returns nothing
set table = TableArray[6]
endmethod
endmodule
struct AnimationManager extends array
implement AnimationManagerInit
endstruct
function UnitAddAnimationLoop takes unit whichunit, string whichanim, real interval returns nothing
call AnimationManager[whichunit].setAnimLoop(whichanim, interval)
endfunction
function UnitRemoveAnimationLoop takes unit whichunit returns nothing
call AnimationManager[whichunit].setAnimLoop("", 1)
endfunction
endlibrary
library OvermindSpells requires /*
*/ Table /*
*
*/ AllocationAndLinks /*
*
*/ TimerUtils /*
*
*/ GenericGroup /*
*
*/ SelectionCircle /*
*
*/ PathChecker /*
*
*/
native UnitAlive takes unit id returns boolean
globals
private constant group ENUM_GROUP = CreateGroup()
private unit enum_unit = null
endglobals
//! runtextmacro GroupT("Int", "", "8", "integer", "Integer", "integer", "0")
//! runtextmacro GroupT("FX", "", "10", "effect", "EffectHandle", "effect", "null")
//! runtextmacro GroupT("Image", "", "8", "image", "ImageHandle", "image", "null")
private function FXGroup_onDestroy takes nothing returns nothing
call DestroyEffect(FXGroup.getRemovedNode().effect)
endfunction
private function CreateFXGroup takes nothing returns FXGroup
local FXGroup new = FXGroup.create()
call new.setOnDestroy(function FXGroup_onDestroy)
return new
endfunction
scope SpellOne
/*
* Toxic Illusion
* - Hides the caster for a period of time, rendering the caster invulnerable.
* The caster can actually gain experience due to the caster having its
* position updated every tick.
*
* - Upon death of illusion, the caster spawns at the target location when the
* spell was cast.
*/
private struct Spell extends array
// Basic modules
implement AllocLink
// Member variables and configurables
// The timer which will be ran when there are any active instances.
// The order cast by dummy
private static constant integer ILLUSION_ORDER = 852274
// The spell id to trigger creation
private static constant integer SPELL_ID = 'A000'
// The buff id to act as an identifier
private static constant integer BUFF_ID = 'B000'
// The ability cast by dummy
private static constant integer ILLUSION_ABILITY = '0000'
// The set timer interval for TIMER_CHECK
private static constant real INTERVAL = 1/32.
// The set timer interval for the venom
private static constant real VENOM_INTERVAL = 1.
private static timer TIMER_CHECK = null
private static unit DUMMY_CASTER = null
// Global parameters
private static real array parameters
private static thistype current = 0
// Main information
// Casting unit
private unit unit
// Illusion
private unit illu
// target
private unit target
// Level of ability
private integer level
// Additional data
private IntGroup metaData
private RealGroup metaDataR
// Cannot be explained properly yet.
private real tickReal
//
private timer time
/* Meta-data information *
/ Generates a method operator index abstracting a call to variable Num01 *
/ which will return the struct Spell, requiring a reference to the *
/ hashtable Table through a submethod integer. The method operator is *
/ then treated privately, which does not allow any other script to *
/ refer to it. The null type is defined as 0. */
//! runtextmacro op_set("index", "Num01", "thistype", "integer", "private", "0")
// Read methods
// FADE_DURATION returns the amount of time needed to make
private static method FADE_DURATION takes integer lev returns real
return 2.5
endmethod
// Returns the default vertex color of the unit.
// 0 - Red, 1 - Green, 2 - Blue, 3 - Alpha
private static method COLOR takes integer req returns integer
if req == 0 then
return 255
elseif req == 1 then
return 255
elseif req == 2 then
return 255
elseif req == 3 then
return 255
endif
return 0
endmethod
private static method FINAL_COLOR takes integer req returns integer
if req == 0 then
return 255
elseif req == 1 then
return 255
elseif req == 2 then
return 255
elseif req == 3 then
return 0
endif
return 0
endmethod
private static method MODEL_FILE takes integer req returns string
// The Corrosive breath model for the venomous fountain.
if req == 0 then
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
// The poison fountain. Unfortunately, a cloud will have to suffice.
// This will be updated in the future
elseif req == 1 then
return "Abilities\\Spells\\Human\\CloudOfFog\\CloudOfFog.mdl"
endif
return ""
endmethod
/* The speed of the venomous projectile */
private static method VENOM_SPEED takes nothing returns real
return 900.
endmethod
/* The enumeration radius for dealing damage */
private static method VENOM_RADIUS takes integer level returns real
if level == 1 then
return 200.
elseif level == 2 then
return 225.
elseif level == 3 then
return 250.
endif
return 0.
endmethod
/* The amount of damage dealt */
private static method VENOM_DAMAGE takes integer level returns real
if level == 1 then
return 25.
elseif level == 2 then
return 40.
elseif level == 3 then
return 55.
endif
return 0.
endmethod
/* The duration of the illusion */
private static method VENOM_COPY_DURATION takes integer level returns real
if level == 1 then
return 15.
elseif level == 2 then
return 20.
elseif level == 3 then
return 25.
endif
return 0.
endmethod
private static method VENOM_COPY_DAMAGE_RATIO takes integer level returns real
// 3*Instruction + level
// Instruction: 0 (Damage dealt)
if level == 1 then
return 1.0
elseif level == 2 then
return 1.0
elseif level == 3 then
return 1.0
// Instruction: 1 (Damage received)
elseif level == 4 then
return 2.0
elseif level == 5 then
return 1.4
elseif level == 6 then
return 0.7
endif
return 0.
endmethod
private static method FOUNTAIN_DAMAGE takes integer level returns real
if level == 1 then
return 100./FADE_DURATION(level)*INTERVAL
elseif level == 2 then
return 140./FADE_DURATION(level)*INTERVAL
elseif level == 3 then
return 180./FADE_DURATION(level)*INTERVAL
endif
return 0.
endmethod
// Destructor
private method destroy takes nothing returns nothing
call SetUnitInvulnerable(this.unit, false)
call PauseUnit(this.unit, false)
call this.metaDataR.destroy()
call this.metaData.destroy()
set this.unit = null
set this.tickReal = 0.
set this.metaDataR = 0
set this.metaData = 0
call this.pop()
call this.deallocate()
endmethod
// onFinish methods
/* Damage callback
* This damages nearby units every 1/50. seconds, dealing about a total of
* 100/140/180 damage over 2.5 seconds. */
private static method onFinish_Damage takes nothing returns nothing
local player owner = GetOwningPlayer(current.unit)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(current.unit), GetUnitY(current.unit), VENOM_RADIUS(current.level), null)
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
// Begin loop
if IsUnitEnemy(enum_unit, owner) and UnitAlive(enum_unit) and not IsUnitType(enum_unit, UNIT_TYPE_MECHANICAL) then
call UnitDamageTarget(current.unit, enum_unit, FOUNTAIN_DAMAGE(current.level), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
endif
// End loop
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
endmethod
// Meta-Data methods
/* This updates the color of the Overmind (as a transition method) */
private method metaData_update takes nothing returns nothing
local real fade_duration = FADE_DURATION(this.level)
local real array color_disparity
local integer i = 1
if this.metaData.integer == 1 then
loop
exitwhen i > 4
set color_disparity[i] = this.metaData[i].integer - (COLOR(i - 1) - FINAL_COLOR(i - 1))/fade_duration*INTERVAL
set this.metaData[i].integer = R2I(color_disparity[i])
set i = i + 1
endloop
elseif this.metaData.integer == 4 then
loop
exitwhen i > 4
set color_disparity[i] = this.metaData[i].integer + (COLOR(i - 1) - FINAL_COLOR(i - 1))/fade_duration*INTERVAL
set this.metaData[i].integer = R2I(color_disparity[i])
set i = i + 1
endloop
endif
endmethod
/* This initializes the color data.
* - each add call allocates a space for the metaData group.
* - Here, we initialize the following: Red, Green, Blue and Alpha
* - see COLOR function for more details on initial color.
*/
private method metaData_init takes nothing returns nothing
set this.metaData.integer = 1
call this.metaData.add(COLOR(0))
call this.metaData.add(COLOR(1))
call this.metaData.add(COLOR(2))
call this.metaData.add(COLOR(3))
endmethod
// Unit methods
/*
This hides the unit who cast the spell SPELL_ID, and spawns an identical illusion
*/
private method unit_createIllu takes nothing returns nothing
call SetUnitX(DUMMY_CASTER, GetUnitX(this.unit))
call SetUnitY(DUMMY_CASTER, GetUnitY(this.unit))
call SetUnitInvulnerable(this.unit, false)
call SetUnitOwner(DUMMY_CASTER, GetOwningPlayer(this.unit), false)
call IssueTargetOrderById(DUMMY_CASTER, ILLUSION_ORDER, this.unit)
call SetUnitOwner(DUMMY_CASTER, Player(PLAYER_NEUTRAL_PASSIVE), false)
call SetUnitInvulnerable(this.unit, true)
call ShowUnit(this.unit, false)
set this.illu = bj_lastCreatedUnit
call UnitApplyTimedLife(this.illu, 'B000', VENOM_COPY_DURATION(this.level))
call SetUnitX(this.illu, GetUnitX(this.unit))
call SetUnitY(this.illu, GetUnitY(this.unit))
if GetLocalPlayer() == GetOwningPlayer(this.unit) then
call SelectUnit(this.illu, true)
endif
set thistype(GetHandleId(this.illu)).index = this
endmethod
// Venom illusion methods
private method venomCopy_onDeath takes nothing returns nothing
local FXGroup fx = this.metaData[5].integer
call ReleaseTimer(this.time)
call fx.destroy()
call this.metaData.node_pop(5)
call ShowUnit(this.unit, true)
call SetUnitX(this.unit, this.metaDataR[1].real)
call SetUnitY(this.unit, this.metaDataR[2].real)
set thistype(GetHandleId(this.illu)).index = 0
set this.time = null
set this.illu = null
set this.tickReal = FADE_DURATION(this.level)
set this.metaData.integer = 4
endmethod
// Venom methods
private static method venom_periodic takes nothing returns nothing
local player owner
local timer time = GetExpiredTimer()
local thistype this = GetTimerData(time)
local FXGroup fx = this.metaData[5].integer
set owner = GetOwningPlayer(this.unit)
call fx.node_pop(1)
call fx.add_pos( AddSpecialEffect(MODEL_FILE(0), this.metaDataR[1].real, this.metaDataR[2].real), 1)
call GroupEnumUnitsInRange(ENUM_GROUP, this.metaDataR[1].real, this.metaDataR[2].real, VENOM_RADIUS(this.level), null)
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
// Begin block
if IsUnitEnemy(enum_unit, owner) and UnitAlive(enum_unit) and not IsUnitType(enum_unit, UNIT_TYPE_MECHANICAL) then
call UnitDamageTarget(this.illu, enum_unit, VENOM_DAMAGE(this.level), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
endif
// End block
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
set time = null
endmethod
private static method venom_onHit takes nothing returns nothing
local thistype this = R2I( Movement.current.metaData[1].real)
local FXGroup fx = R2I( Movement.current.metaData[2].real)
call this.metaDataR.add(GetUnitX(Movement.current.unit))
call this.metaDataR.add(GetUnitY(Movement.current.unit))
call this.metaData.add(fx)
call TimerStart(time, VENOM_INTERVAL, true, function thistype.venom_periodic)
call fx.clear()
call fx.add( AddSpecialEffect(MODEL_FILE(0), this.metaDataR[1].real, this.metaDataR[2].real))
call fx.add( AddSpecialEffect(MODEL_FILE(1), this.metaDataR[1].real, this.metaDataR[2].real))
call DummyAddRecycleTimer(Movement.current.unit, 1.0)
call Movement.current.destroy()
endmethod
// Exclusively for the struct!
private static method onTick takes nothing returns nothing
local thistype this = thistype(0).next
local Movement miss = 0
local FXGroup fx = 0
loop
exitwhen this == 0
if this.metaData.integer == 1 then
call this.metaData_update()
call DestroyEffect( AddSpecialEffect(MODEL_FILE(0), GetUnitX(this.unit), GetUnitY(this.unit)) )
call SetUnitVertexColor(this.unit, this.metaData[1].integer, this.metaData[2].integer, /*
*/ this.metaData[3].integer, this.metaData[4].integer)
set this.tickReal = this.tickReal - INTERVAL
if ModuloReal(this.tickReal, 1.00) == 0.50 then
call SetUnitAnimation(this.unit, "spell")
call QueueUnitAnimation(this.unit, "stand")
endif
if this.tickReal <= 0. then
set this.tickReal = 0.
set this.metaData.integer = 2
endif
elseif this.metaData.integer == 2 then
call this.unit_createIllu()
set parameters[0] = GetUnitX(this.unit)
set parameters[1] = GetUnitY(this.unit)
set parameters[2] = Atan2(GetUnitY(this.target) - parameters[1], GetUnitX(this.target) - parameters[0])*bj_RADTODEG
call GetRecycledDummy(parameters[0], parameters[1], 50, parameters[2])
call PauseUnit(bj_lastCreatedUnit, true)
call DestroyEffect( AddSpecialEffect(MODEL_FILE(0), parameters[0], parameters[1]) )
set fx = CreateFXGroup()
call fx.add(AddSpecialEffectTarget(MODEL_FILE(0), bj_lastCreatedUnit, "origin"))
set miss = Movement.add(bj_lastCreatedUnit)
set miss.speed = VENOM_SPEED()
call miss.metaData.add(this)
call miss.metaData.add(fx)
call miss.setExpireListener(function thistype.venom_onHit)
call miss.aimUnit(this.target)
set this.target = null
set this.metaData.integer = 3
elseif this.metaData.integer == 3 then
call SetUnitX(this.unit, GetUnitX(this.illu))
call SetUnitY(this.unit, GetUnitY(this.illu))
elseif this.metaData.integer == 4 then
call this.metaData_update()
if ModuloReal(this.tickReal, 1.00) == 0.50 then
call SetUnitAnimation(this.unit, "spell")
call QueueUnitAnimation(this.unit, "stand")
endif
set this.tickReal = this.tickReal - INTERVAL
if this.tickReal <= 0. then
call this.destroy()
else
call DestroyEffect( AddSpecialEffect(MODEL_FILE(0), GetUnitX(this.unit), GetUnitY(this.unit)) )
call SetUnitVertexColor(this.unit, this.metaData[1].integer, this.metaData[2].integer, /*
*/ this.metaData[3].integer, this.metaData[4].integer)
set current = this
call ForForce(bj_FORCE_PLAYER[0], function thistype.onFinish_Damage)
endif
endif
set this = this.next
endloop
endmethod
private static method create takes nothing returns thistype
local thistype this = 0
set this = allocate()
set this.unit = GetTriggerUnit()
set this.level = GetUnitAbilityLevel(this.unit, SPELL_ID)
set this.tickReal = FADE_DURATION(this.level)
set this.target = GetSpellTargetUnit()
set this.metaData = IntGroup.create()
set this.metaDataR = RealGroup.create()
set this.time = NewTimerEx(this)
call this.metaData_init()
call this.push()
call SetUnitAnimation(this.unit, "spell")
call SetUnitInvulnerable(this.unit, true)
call PauseUnit(this.unit, true)
if GetTimerData(TIMER_CHECK) == 0 then
call TimerStart(TIMER_CHECK, INTERVAL, true, function thistype.onTick)
endif
call SetTimerData(TIMER_CHECK, GetTimerData(TIMER_CHECK) + 1)
return this
endmethod
private static method onSpellCast takes nothing returns nothing
if GetSpellAbilityId() == SPELL_ID then
call create()
endif
endmethod
private static method onDummySummon takes nothing returns nothing
set bj_lastCreatedUnit = GetSummonedUnit()
endmethod
private static method onIllusionDeath takes nothing returns nothing
local thistype this = thistype(GetHandleId(GetTriggerUnit())).index
if IsUnitIllusion(GetTriggerUnit()) then
call venomCopy_onDeath()
endif
endmethod
private static method onIllusionDamage takes nothing returns nothing
local thistype array this
set this[0] = thistype(GetHandleId(Damage.source)).index
set this[1] = thistype(GetHandleId(Damage.target)).index
if this[0] != 0 and IsUnitIllusion(Damage.source) and GetUnitAbilityLevel(Damage.source, BUFF_ID) != 0 then
set Damage.amount = Damage.amount*VENOM_COPY_DAMAGE_RATIO(3*0 + this[0].level)
endif
if this[1] != 0 and IsUnitIllusion(Damage.target) and GetUnitAbilityLevel(Damage.target, BUFF_ID) != 0 then
set Damage.amount = Damage.amount*VENOM_COPY_DAMAGE_RATIO(3*1 + this[1].level)
endif
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function thistype.onSpellCast)
set TIMER_CHECK = NewTimerEx(0)
set DUMMY_CASTER = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'dumi', 0, 0, 0)
call UnitAddAbility(DUMMY_CASTER, ILLUSION_ABILITY)
set t = CreateTrigger()
call TriggerRegisterUnitEvent(t, DUMMY_CASTER, EVENT_UNIT_SUMMON)
call TriggerAddCondition(t, function thistype.onDummySummon)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, function thistype.onIllusionDeath)
call Damage.registerModifier(function thistype.onIllusionDamage)
set t = null
endmethod
endstruct
endscope
scope SpellTwo
/*
* Fel Magic Synthesis
* - Each damage dealt by the Overmind is converted to additional mana.
*
* - Includes a sub-spell that updates its' information every time the
* ability is skilled.
*
* - Every damage instance will count if and only if the damage exceeds
* the minimum amount. This applies to spider generation.
*
*/
private struct Spell extends array
// Basic allocator module.
implement AllocT
// Member variables and configurables
// The learned ability, and a flag for registry.
private static constant integer SPELL_ID = 'A001'
// The sub-ability added on learning
private static constant integer SUB_SPELL = '1000'
//! runtextmacro op_set("index", "Num02", "thistype", "integer", "private", "0")
// The learning unit
private unit unit
// The level of the ability SPELL_ID
private integer level
// The amount of spiders to be spawned.
private integer spiderStack
// Read methods
/* This returns the percentage of damage that becomes additional mana. */
private static method MANA_BONUS takes integer lev returns real
if lev == 1 then
return 0.07
elseif lev == 2 then
return 0.1
elseif lev == 3 then
return 0.13
endif
return 0.
endmethod
/* Returns the minimum amount of damage to be considered as a damage instance */
private static method MIN_DAMAGE_ACCEPT takes integer lev returns real
if lev == 1 then
return 30.
elseif lev == 2 then
return 25.
elseif lev == 3 then
return 20.
endif
return 0.
endmethod
/* Returns the amount of damage instances required to generate one spider */
private static method DMG_INSTANCE_SPIDER takes integer lev returns integer
if lev == 1 then
return 7
elseif lev == 2 then
return 6
elseif lev == 3 then
return 1 //5
endif
/* Unobtainable constant. Will cause mal-functioning of the skill if violated
* return 2^31 - 1 */
return 2147483647
endmethod
/* Returns the maximum number of spiders for each level */
private static method SPIDER_MAXIMUM_COUNT takes integer lev returns integer
if lev == 1 then
return 3
elseif lev == 2 then
return 3
elseif lev == 3 then
return 4
endif
return 1
endmethod
/* How long will the spider remain alive? */
private static method SPIDER_DURATION takes integer lev returns real
if lev == 1 then
return 10.
elseif lev == 2 then
return 15.
elseif lev == 3 then
return 20.
endif
return 0.
endmethod
/* Returns the raw-code of the spiders to be spawned. */
private static method SPIDER_ID takes integer lev returns integer
if lev == 1 then
return 'e003'
elseif lev == 2 then
return 'e004'
elseif lev == 3 then
return 'e005'
endif
return 0
endmethod
/* The spawn distance of the spiders */
private static method SPIDER_SPAWN_DIST takes integer level returns real
if level == 1 then
return GetRandomReal(350., 400.)
elseif level == 2 then
return GetRandomReal(350., 500.)
elseif level == 3 then
return GetRandomReal(400., 600.)
endif
return 0.
endmethod
/* The amount of time taken for the spider/s to spawn */
private static method SPIDER_SPAWN_DELAY takes nothing returns real
return 1.
endmethod
/* Missile speed */
private static method MISSILE_SPEED takes nothing returns real
return 400.
endmethod
/* This stores paths for model files */
private static method MODEL_FILE takes integer req returns string
if req == 0 then
// The egg missile, generated when casting the sub-spell.
return "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
elseif req == 1 then
// The splash missile, played upon hitting.
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif req == 2 then
return "Doodads\\Dungeon\\Terrain\\EggSack\\EggSack0.mdl"
endif
return ""
endmethod
// Exclusive to the struct!
// Getter of instance.
private static method getUnitIndex takes unit u returns thistype
return thistype(GetHandleId(u)).index
endmethod
// on Damage methods
/* Called on damage */
private method onDamage_update takes nothing returns nothing
call SetUnitState(this.unit, UNIT_STATE_MANA, GetUnitState(this.unit, UNIT_STATE_MANA) /*
*/ + Damage.amount*MANA_BONUS(this.level) )
if Damage.amount >= MIN_DAMAGE_ACCEPT(this.level) then
set this.spiderStack = IMinBJ(this.spiderStack + 1, /*
*/ DMG_INSTANCE_SPIDER(this.level)*SPIDER_MAXIMUM_COUNT(this.level))
endif
endmethod
// Spider methods
// Called when the egg sack explodes.
private static method spider_onSpawn takes nothing returns nothing
local timer temp = GetExpiredTimer()
local RealGroup tempData = ReleaseTimer(temp)
local thistype this = R2I( tempData[1].real)
local FXGroup fx = R2I( tempData[2].real)
call fx.destroy()
set bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(this.unit), SPIDER_ID(this.level), tempData[3].real, tempData[4].real, GetRandomReal(0., 360.))
call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', SPIDER_DURATION(this.level))
call tempData.destroy()
set temp = null
endmethod
// Missile methods
// Called when missile hits the target area.
private static method missile_onHit takes nothing returns nothing
local thistype this = R2I( Movement.current.metaData[1].real)
local FXGroup fx = R2I( Movement.current.metaData[2].real)
local RealGroup temp = RealGroup.create()
local timer tempTimer = NewTimerEx(temp)
call fx.add( AddSpecialEffect(MODEL_FILE(1), GetUnitX(Movement.current.unit), GetUnitY(Movement.current.unit)))
call fx.clear()
call temp.add(this)
call temp.add(fx)
call temp.add(GetUnitX(Movement.current.unit))
call temp.add(GetUnitY(Movement.current.unit))
call fx.add( AddSpecialEffect(MODEL_FILE(2), GetUnitX(Movement.current.unit), GetUnitY(Movement.current.unit)))
call TimerStart(tempTimer, SPIDER_SPAWN_DELAY(), false, function thistype.spider_onSpawn)
call DummyAddRecycleTimer(Movement.current.unit, 2.00)
call Movement.current.destroy()
set tempTimer = null
endmethod
// unit methods
// Called when casting the sub-spell.
private static method unit_spawnSpiders takes nothing returns nothing
local thistype this = getUnitIndex(GetTriggerUnit())
local integer instances = 0
local Movement move = 0
local FXGroup fx = 0
local real cx = 0.
local real cy = 0.
local real tx = 0.
local real ty = 0.
if this.spiderStack < DMG_INSTANCE_SPIDER(this.level) then
call DisplayTextToPlayer(GetOwningPlayer(this.unit), 0, 0, "|cffffcc00Not enough damage instances!|r")
return
endif
set cx = GetUnitX(this.unit)
set cy = GetUnitY(this.unit)
call GetRandomWalkablePaths(cx, cy, SPIDER_SPAWN_DIST(this.level), 12)
loop
exitwhen this.spiderStack < DMG_INSTANCE_SPIDER(this.level) or (GetRandomWalkablePathsAmount() <= 0)
set instances = GetRandomInt(1, GetRandomWalkablePathsAmount())
set tx = GetWalkablePathX(instances)
set ty = GetWalkablePathY(instances)
set fx = CreateFXGroup()
call PopWalkablePath(instances)
call GetRecycledDummy(cx, cy, 0, /*
*/ Atan2(ty - cy, tx - cx)*bj_RADTODEG)
set move = Movement.add(bj_lastCreatedUnit)
set move.speed = MISSILE_SPEED()
call move.mark(tx, ty)
call move.metaData.add(this)
call move.metaData.add(fx)
call fx.add( AddSpecialEffectTarget(MODEL_FILE(0), move.unit, "origin"))
call move.setExpireListener(function thistype.missile_onHit)
set move.arc = 3.00
set instances = instances + 1
set this.spiderStack = this.spiderStack - DMG_INSTANCE_SPIDER(this.level)
endloop
endmethod
private static method create takes nothing returns thistype
local thistype this = getUnitIndex(GetTriggerUnit())
if IsUnitIllusion(GetTriggerUnit()) then
return 0
endif
if this == 0 then
set this = allocate()
set this.unit = GetTriggerUnit()
call UnitAddAbility(this.unit, SUB_SPELL)
// Just in case someone adds a transformation ability.
call UnitMakeAbilityPermanent(this.unit, true, SUB_SPELL)
set thistype(GetHandleId(this.unit)).index = this
endif
set this.level = GetUnitAbilityLevel(this.unit, SPELL_ID)
call SetUnitAbilityLevel(this.unit, SUB_SPELL, this.level)
return this
endmethod
private static method onSkillLearn takes nothing returns nothing
if GetLearnedSkill() == SPELL_ID then
call create()
endif
endmethod
private static method onSubSpellCast takes nothing returns nothing
if GetSpellAbilityId() == SUB_SPELL then
call unit_spawnSpiders()
endif
endmethod
private static method onDamage takes nothing returns nothing
local thistype this = getUnitIndex(Damage.source)
/* Make sure that the damaging unit is not an illusion */
if this != 0 and not IsUnitIllusion(Damage.source) then
call this.onDamage_update()
endif
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
call TriggerAddCondition(t, function thistype.onSkillLearn)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function thistype.onSubSpellCast)
call Damage.register(function thistype.onDamage)
set t = null
endmethod
endstruct
endscope
scope SpellThree
/* Warning: A pain to read
*/
private constant function SPELL_ID takes nothing returns integer
return 'A002'
endfunction
private constant function RAW_ID takes integer request returns integer
if request == 0 then
// Returns the ID of the hatchery.
return 'e001'
elseif request == 1 then
// Returns the ID of the fel spiderlings
return 'e002'
elseif request == 2 then
// Returns the ID of the sub-spell
return '1001'
elseif request == 3 then
// Returns the hex value of the selection circle.
// Used by the selection Circle
// Goes by Red, Green, Blue, and Alpha values (with maximum for each being 0xFF)
return 0xFFFFFFFF
elseif request == 4 then
// Returns the hex value of another selection circle. (on Death)
return 0xFF0000FF
elseif request == 5 then
// Returns a dotted green selection circle. (Displays target acquisition)
return 0x00FF00FF
endif
return 0
endfunction
private constant function DATA takes integer request, integer level returns real
if request == 0 then
// Returns the duration of the hive
if level == 1 then
return 50.
elseif level == 2 then
return 75.
elseif level == 3 then
return 100.
endif
elseif request == 1 then
// Returns the effective detection range.
if level == 1 then
return 150.
elseif level == 2 then
return 250.
elseif level == 3 then
return 350.
endif
elseif request == 2 then
// Returns the tick rate.
// Defaults to 1.
return 1.
elseif request == 3 then
// Returns the amount of mana burned around the hive
if level == 1 then
return 5.
elseif level == 2 then
return 7.
elseif level == 3 then
return 10.
endif
elseif request == 4 then
// Returns the amount of health rejuvenated by ratio.
// Only takes effect when used as a nydus canal.
if level == 1 then
return 0.1
elseif level == 2 then
return 0.12
elseif level == 3 then
return 0.16
endif
elseif request == 5 then
// Returns the range field for allowing mana burn
// Defaults to 500.
return 500.
elseif request == 6 then
// Returns the flag for allowing the hive to be invulnerable.
// Defaults to true (not equal to 0.)
return 0.
elseif request == 7 then
// Returns the damage dealt when hive (simulating death or actually dying)
if level == 1 then
return 85.
elseif level == 2 then
return 150.
elseif level == 3 then
return 220.
endif
elseif request == 8 then
// Returns the amount of spiders that will spawn.
if level == 1 then
return 4.
elseif level == 2 then
return 5.
elseif level == 3 then
return 6.
endif
elseif request == 9 then
// Returns the speed of the missile
// Must correspond to the object data equivalent.
return 1000.
elseif request == 10 then
// Returns the frequency of the global timer
// Cannot be configured to the level of the unit
return 1/32.
elseif request == 11 then
// Returns the fade-in total duration.
// Cannot be configured to the level of the unit
return 2.5
elseif request == 12 then
// Returns the duration of the death animation
// Cannot be configured to the level of the unit
return 5.
elseif request == 13 then
// Returns the main attribute to follow
// Higher values will return modulo of 3.
// Strength: 1
// Agility: 2
// Intelligence: 3
return 3.
elseif request == 14 then
// Returns damage dealt based on percentage
if level == 1 then
return 0.1
elseif level == 2 then
return 0.14
elseif level == 3 then
return 0.18
endif
elseif request == 15 then
// Returns the amount of mana rejuvenated by ratio.
// Only takes effect when used as a nydus canal.
if level == 1 then
return 0.12
elseif level == 2 then
return 0.16
elseif level == 3 then
return 0.2
endif
elseif request == 16 then
// Returns the distance offset of a targeted unit.
if level == 1 then
return DATA(1, level) + 150.
elseif level == 2 then
return DATA(1, level) + 175.
elseif level == 3 then
return DATA(1, level) + 200.
endif
elseif request == 17 then
// Returns the distance check for notifying the player of a potential target.
// Cannot be configured to the level of the unit.
return 3000.
elseif request == 18 then
// Returns the time delay in updating the text warnings.
// Cannot be configured to the level of the unit.
return 15.
elseif request == 19 then
// Returns the amount of time to display a selection circle on the eruption of a hive.
// Cannot be configured to the level of the unit.
return 0.2
elseif request == 20 then
// Returns the amount of time (delay) for the teleportation of the unit.
// Cannot be configured to the level of the unit.
return 2.5
endif
return 0.
endfunction
/* Generated boolean comparison (inlined) */
globals
private constant boolean REG_11_BOOL = 2.5 <= 0.
endglobals
/* The strings which will be used for projecting certain effects and such */
private constant function MDL_FILE takes integer request returns string
if request == 0 then
// Chimaera Acid
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif request == 1 then
// Mana burn effect
return "Abilities\\Spells\\NightElf\\ManaBurn\\ManaBurnTarget.mdl"
elseif request == 2 then
// Snap missile (Virtually useless)
return "Abilities\\Weapons\\snapMissile\\snapMissile.mdl"
elseif request == 3 then
// Detonate effect
return "Units\\NightElf\\Wisp\\WispExplode.mdl"
elseif request == 4 then
// Talk to me sign, denotes target of the hive.
return "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"
elseif request == 5 then
// Highlights the hives that are generated by the Fel spider
return "Abilities\\Spells\\Other\\Parasite\\ParasiteTarget.mdl"
elseif request == 6 then
// Special effect to be played when teleported.
return "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
elseif request == 7 then
// Special effect to be played when teleporting.
return "Abilities\\Spells\\Undead\\DeathPact\\DeathPactTarget.mdl"
elseif request == 8 then
// Special effect to be played as some sort of eruption effect.
return MDL_FILE(6)
endif
return ""
endfunction
private function real_GetDist takes real x1, real y1, real x2, real y2 returns real
return SquareRoot((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2))
endfunction
private function echo takes string s returns nothing
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 16, s)
endfunction
private function line_Break takes nothing returns nothing
call echo(" ")
endfunction
// Generates a module with the name DoubleLink_local. It has the property of a private module.
// Same with the next textmacro. A private module DoubleLink_active is generated.
//! runtextmacro DLinkedListT("local", "private")
//! runtextmacro DLinkedListT("active", "private")
private struct Data extends array
implement AllocLinkBundle
implement DoubleLink_local
implement DoubleLink_active
private static constant timer ACTIVE_TIMER = CreateTimer()
private static constant group STORED_SPIDERS = CreateGroup()
// Assistant to the stored spider group
private static integer stored_spider_count = 0
// Counter for a forGroupEx call
private static integer spider_group_count = 0
// Mode checker
private integer mode
private real mode_data
/* Part One
* - This will store the unit, level, timer, target x, and target y for
* the successful creation of the hive. */
private unit unit
private unit hive
private integer level
private timer timer2
private timer timer
private real tx
private real ty
private effect hive_effect
// Part Two
/* - This will be the main part of the spell, the mechanics of which will
* be the main focus and intention of this spell. */
// For speed purposes
private static player feedback_player
private static thistype hive_this
private static thistype spider_this
private real stored
private integer eventtype
private unit target
private trigger detector
private group spider_group
private effect head_effect
static if LIBRARY_SelectionCircle then
private SelectionCircle circle
private SelectionCircle circle2
endif
/*
These textmacros can be found at CustomFunctions. It's easy to port them to this library, though.
They generate a searching algorithm for finding a certain instance.
If not fully explained, the parameters are defined as follows:
search_list(encapsulation-keyword, name, searched type, iterator variable, conditional statement)
*/
//! runtextmacro search_list("private", "get", "unit", "next", "unit == id")
//! runtextmacro search_list("private", "get_hive", "unit", "active_next", "hive == id")
//! runtextmacro search_list("private", "get_targ", "unit", "active_next", "target == id")
//! runtextmacro search_list("private", "get_trig", "trigger", "active_next", "detector == id")
//! runtextmacro search_list("private", "get_group", "unit", "active_next", "IsUnitInGroup(id, spider_group)")
// Detaches a certain instance from a global list if it is found and in the local list.
// Pushes the next local instance to the global list if there exists at least one more.
private method detach takes nothing returns nothing
if head != 0 then
if local_next != this then
set local_next.head = 1
call local_next.insert(next)
endif
set head = 0
call pop()
endif
call local_pop()
endmethod
/* Mimics death for the spiders */
private static method spider_preDestroy_enum takes nothing returns nothing
set enum_unit = GetEnumUnit()
call PauseUnit(enum_unit, true)
call SetUnitAnimation(enum_unit, "death")
endmethod
private method spider_preDestroy takes nothing returns nothing
call ForGroup(spider_group, function thistype.spider_preDestroy_enum)
set enum_unit = null
endmethod
/* Resets the animation for the next group */
private static method spider_onDestroy_enum takes nothing returns nothing
set enum_unit = GetEnumUnit()
call QueueUnitAnimation(enum_unit, "stand")
call SetUnitX(enum_unit, 0)
call SetUnitY(enum_unit, 0)
call SetUnitOwner(enum_unit, Player(15), true)
call ShowUnit(enum_unit, false)
endmethod
private method spider_onDestroy takes nothing returns nothing
call ForGroup(spider_group, function thistype.spider_onDestroy_enum)
set enum_unit = null
endmethod
/* Check if the hive is still alive. If not, attempt to nullify hive and prepare a new one. */
private method preDestroy takes nothing returns nothing
if not UnitAlive(hive) then
call RemoveUnit(hive)
set hive = null
else
call PauseUnit(hive, true)
call ShowUnit(hive, false)
call SetUnitX(hive, 0)
call SetUnitY(hive, 0)
call SetWidgetLife(hive, GetUnitState(hive, UNIT_STATE_MAX_LIFE))
call SetUnitOwner(hive, Player(15), false)
if not (DATA(6, level) != 0) then
call UnitRemoveAbility(hive, 'Aloc')
endif
endif
endmethod
private method destroy takes nothing returns nothing
static if ADV_DEBUG then
call echo("Deallocating the instance: thistype(" + I2S(this) + ")")
endif
call spider_onDestroy()
call preDestroy()
call ReleaseTimer(timer)
call ReleaseTimer(timer2)
call DestroyTrigger(detector)
if GetHandleId(head_effect) != 0 then
call echo("Instance member error: Effect has not yet been destroyed!")
call echo("Please notify the spell creator immediately!")
call line_Break()
call DestroyEffect(head_effect)
endif
debug if GetUnitAbilityLevel(unit, RAW_ID(2)) != 0 and local_next == this and get(unit) == 0 then
debug call echo("Ability error: Ability has not been removed!")
debug call echo("Please notify the spell creator immediately!")
debug call line_Break()
debug endif
set unit = null
set target = null
set timer2 = null
set timer = null
set detector = null
set head_effect = null
set tx = 0.
set ty = 0.
set mode_data = 0.
set stored = 0.
static if LIBRARY_SelectionCircle then
set circle = 0
set circle2 = 0
endif
set head = 0
set mode = 0
set eventtype = 0
call active_pop()
set active_head = 0
set thistype(0).active_head = thistype(0).active_head - 1
call deallocate()
endmethod
// Part two
private static code target_search_code = null
private static method target_attack_enum takes nothing returns nothing
local thistype this = spider_this
set enum_unit = GetEnumUnit()
if target != null then
if UnitAlive(target) then
call IssueTargetOrder(enum_unit, "attack", target)
else
if GetHandleId(head_effect) != 0 then
call DestroyEffect(head_effect)
endif
set target = null
endif
else
call IssuePointOrder(enum_unit, "move", GetUnitX(hive) + GetRandomReal(-DATA(1, level), DATA(1, level)), GetUnitY(hive) + GetRandomReal(-DATA(1, level), DATA(1, level)))
endif
endmethod
private method target_attack takes nothing returns nothing
local real rx = GetUnitX(hive)
local real ry = GetUnitY(hive)
set spider_this = this
if (target != null and real_GetDist(rx, ry, GetUnitX(target), GetUnitY(target)) > DATA(16, level)) then
call ForForce(bj_FORCE_PLAYER[0], target_search_code)
endif
call ForGroup(spider_group, function thistype.target_attack_enum)
set spider_this = 0
endmethod
private method target_search_onFinish takes nothing returns nothing
local real rx = GetUnitX(target)
local real ry = GetUnitY(target)
if GetLocalPlayer() == GetOwningPlayer(hive) then
if real_GetDist(GetCameraTargetPositionX(), GetCameraTargetPositionY(), GetUnitX(hive), GetUnitY(hive)) > DATA(17, 0) then
call PingMinimapEx(rx, ry, 3., 255, 0, 0, true)
call DisplayTextToPlayer(GetOwningPlayer(hive), -0.5, 0, "|cffffcc00A target has been found!")
endif
endif
endmethod
private method target_search takes nothing returns nothing
set feedback_player = GetOwningPlayer(hive)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(hive), GetUnitY(hive), DATA(1, level), null)
call GroupRemoveUnit(ENUM_GROUP, target)
call DestroyEffect(head_effect)
set head_effect = null
set target = null
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
if UnitAlive(enum_unit) and IsUnitEnemy(enum_unit, feedback_player) then
if UnitDamageTarget(enum_unit, enum_unit, 0, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null) then
static if ADV_DEBUG then
call echo("ENUM_GROUP: Current enum_unit is a viable target!")
call line_Break()
endif
set target = enum_unit
exitwhen true
else
static if ADV_DEBUG then
call echo("ENUM_GROUP: Current enum_unit is not a viable target!")
call line_Break()
endif
endif
endif
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
if enum_unit == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").target_search: No targets in range.")
call echo("thistype(" + I2S(this) + ").target_search: Defaulting to idle spiders.")
call line_Break()
endif
set target = null
else
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").target_search: A target was discovered.")
call line_Break()
endif
set head_effect = AddSpecialEffectTarget(MDL_FILE(4), target, "overhead")
if not (TimerGetRemaining(timer2) != 0) then
call target_search_onFinish()
call TimerStart(timer2, DATA(18, 0), false, null)
endif
endif
endmethod
private static method onTarget_search takes nothing returns nothing
call spider_this.target_search()
endmethod
private static method target_onDeathEvent takes nothing returns nothing
local thistype this = get_targ(GetTriggerUnit())
local thistype that = this.local_next
loop
exitwhen that == this
if that.target == target then
call that.target_search()
call that.target_attack()
endif
set that = that.local_next
endloop
if this != 0 and mode != 3 then
call target_search()
call target_attack()
endif
endmethod
private static method hive_onDeathProxy takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call destroy()
endmethod
static if LIBRARY_SelectionCircle then
private static method selectionCircle_onExpire takes nothing returns nothing
local SelectionCircle sc = GetTimerData(GetExpiredTimer())
call sc.destroy()
endmethod
endif
private static method hive_onErupt takes nothing returns nothing
local thistype this = hive_this
static if LIBRARY_SelectionCircle then
local SelectionCircle sc = SelectionCircle.create(GetUnitX(hive), GetUnitY(hive), DATA(5, level), RAW_ID(4))
endif
set hive_this = 0
set feedback_player = GetOwningPlayer(hive)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(hive), GetUnitY(hive), DATA(5, level), null)
static if LIBRARY_SelectionCircle then
call SetTimerData(timer2, sc)
call TimerStart(timer2, DATA(19, 0), false, function thistype.selectionCircle_onExpire)
endif
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
if UnitAlive(enum_unit) and IsUnitEnemy(enum_unit, feedback_player) then
if UnitDamageTarget(hive, enum_unit, DATA(7, level), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null) then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(6), enum_unit, "chest"))
endif
endif
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
endmethod
private method hive_onDeath takes nothing returns nothing
call PauseTimer(timer)
call PauseTimer(timer2)
call TimerStart(timer, DATA(12, 0), false, function thistype.hive_onDeathProxy)
set mode = 3
if eventtype != 1 then
call SetUnitAnimation(hive, "death")
endif
if UnitAlive(target) then
call DestroyEffect(head_effect)
set head_effect = null
endif
static if LIBRARY_SelectionCircle then
call circle.destroy()
call circle2.destroy()
endif
if local_next == this then
call UnitRemoveAbility(unit, RAW_ID(2))
endif
call detach()
set hive_this = this
call ForForce(bj_FORCE_PLAYER[0], function thistype.hive_onErupt)
call spider_preDestroy()
endmethod
private method overmind_onTeleport takes nothing returns nothing
local real rx = 0.
local real ry = 0.
local player p = GetOwningPlayer(unit)
if this == 0 then
call echo("thistype.overmind_onTeleport: Invalid instance!")
return
endif
set rx = GetUnitX(unit)
set ry = GetUnitY(unit)
call SetWidgetLife(unit, GetWidgetLife(unit) + stored*DATA(4, level))
call SetUnitState(unit, UNIT_STATE_MANA, GetUnitState(unit, UNIT_STATE_MANA) + stored*DATA(15, level))
// For boolean consistency
set eventtype = 3
if not (DATA(6, level) != 0) then
call UnitRemoveBuffsEx(hive, true, true, false, false, true, false, false)
endif
call DestroyEffect(AddSpecialEffect(MDL_FILE(3), rx, ry))
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), rx, ry))
call DestroyEffect(AddSpecialEffect(MDL_FILE(8), GetUnitX(hive), GetUnitY(hive)))
call SetUnitX(unit, GetUnitX(hive))
call SetUnitY(unit, GetUnitY(hive))
static if LIBRARY_TextTag then
if not (stored != 0.) then
call CreateTextTagBJ(p, GetUnitX(hive), GetUnitY(hive), 75, 0, 255, 255, "+" + I2S(R2I(stored*(DATA(4, level)))) + " health!\n+" + I2S(R2I(stored*(DATA(15, level)))) + " mana!")
set vj_lastCreatedTextTag.duration = 3.0
set vj_lastCreatedTextTag.fade = 2.25
endif
endif
// Unimportant to deallocate players (hear-say)
endmethod
private static method overmind_onDelayFinish takes nothing returns nothing
local thistype this = ReleaseTimer(GetExpiredTimer())
call SetUnitInvulnerable(unit, false)
if UnitRemoveAbility(unit, RAW_ID(2)) then
call UnitAddAbility(unit, RAW_ID(2))
call SetUnitAbilityLevel(unit, RAW_ID(2), level)
endif
if mode != 3 then
call IssueImmediateOrderById(unit, 851972)
call overmind_onTeleport()
debug else
static if ADV_DEBUG then
call echo("The hive must have been killed!")
endif
endif
endmethod
private static code hive_onFeedback_loop_code
private method overmind_delayTeleport takes nothing returns nothing
set mode = 4
call PauseTimer(timer)
call TimerStart(timer, DATA(2, level), true, hive_onFeedback_loop_code)
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(7), hive, "overhead"))
call SetUnitInvulnerable(unit, true)
call TimerStart(NewTimerEx(this), RMinBJ(DATA(20, 0), mode_data - DATA(10, 0)), false, function thistype.overmind_onDelayFinish)
endmethod
private static method hive_onDeathEvent takes nothing returns nothing
local thistype this = get_hive(GetTriggerUnit())
if this != 0 then
if eventtype == 0 then
set eventtype = 1
endif
call hive_onDeath()
endif
endmethod
private static method hive_onFeedback_loop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if mode == 2 then
if mode_data > 0. then
call target_attack()
set feedback_player = GetOwningPlayer(hive)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(hive), GetUnitY(hive), DATA(5, level), null)
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
if UnitAlive(enum_unit) and IsUnitEnemy(enum_unit, feedback_player) then
if GetUnitState(enum_unit, UNIT_STATE_MAX_MANA) != 0 then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(1), enum_unit, "chest"))
call SetUnitState(enum_unit, UNIT_STATE_MANA, GetUnitState(enum_unit, UNIT_STATE_MANA) - DATA(3, level))
call UnitDamageTarget(hive, enum_unit, DATA(3, level), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
set stored = stored + DATA(3, level)
endif
endif
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
else
set eventtype = 2
call hive_onDeath()
endif
elseif mode == 4 then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(7), hive, "overhead"))
set mode_data = mode_data - DATA(2, level)
endif
endmethod
private static method hive_onDetectEnemy takes nothing returns nothing
local thistype this = get_trig(GetTriggeringTrigger())
if IsUnitEnemy(GetTriggerUnit(), GetOwningPlayer(hive)) then
if target == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").target: An appropriate target has been found!")
call line_Break()
endif
set target = GetTriggerUnit()
if head_effect != null then
call DestroyEffect(head_effect)
endif
set head_effect = AddSpecialEffectTarget(MDL_FILE(4), target, "overhead")
call target_attack()
endif
endif
endmethod
private static method spider_forGroupEx_enum takes nothing returns nothing
local thistype this = spider_this
set enum_unit = GetEnumUnit()
set spider_group_count = spider_group_count + 1
call SetUnitOwner(enum_unit, feedback_player, true)
call SetUnitPosition(enum_unit, tx, ty)
call SetUnitFacing(enum_unit, GetRandomReal(0, 360))
call PauseUnit(enum_unit, false)
call ShowUnit(enum_unit, true)
call UnitRemoveAbility(enum_unit, 'Aloc')
call UnitAddAbility(enum_unit, 'Aloc')
endmethod
private method spider_forGroupEx takes nothing returns integer
set spider_this = this
set spider_group_count = 0
call ForGroup(spider_group, function thistype.spider_forGroupEx_enum)
set enum_unit = null
return spider_group_count
endmethod
private method hive_spider_init takes nothing returns nothing
local integer int = R2I(DATA(8, level))
set feedback_player = GetOwningPlayer(unit)
if spider_group == null then
set spider_group = CreateGroup()
endif
set int = int - spider_forGroupEx()
if int < 0 then
loop
set enum_unit = FirstOfGroup(spider_group)
exitwhen int >= 0
call SetUnitOwner(enum_unit, Player(15), true)
call SetUnitX(enum_unit, 0)
call SetUnitY(enum_unit, 0)
call PauseUnit(enum_unit, true)
call ShowUnit(enum_unit, false)
call UnitRemoveAbility(enum_unit, 'Aloc')
call UnitAddAbility(enum_unit, 'Aloc')
call GroupRemoveUnit(spider_group, enum_unit)
call GroupAddUnit(STORED_SPIDERS, enum_unit)
set stored_spider_count = stored_spider_count + 1
set int = int + 1
endloop
else
// We check if there are any free spiders in the group.
loop
set enum_unit = FirstOfGroup(STORED_SPIDERS)
exitwhen enum_unit == null or int <= 0 or stored_spider_count <= 0
call SetUnitOwner(enum_unit, feedback_player, true)
call SetUnitPosition(enum_unit, tx, ty)
call SetUnitFacing(enum_unit, GetRandomReal(0, 360))
call PauseUnit(enum_unit, false)
call ShowUnit(enum_unit, true)
call UnitRemoveAbility(enum_unit, 'Aloc')
call UnitAddAbility(enum_unit, 'Aloc')
call GroupRemoveUnit(STORED_SPIDERS, enum_unit)
call GroupAddUnit(spider_group, enum_unit)
set stored_spider_count = stored_spider_count - 1
set int = int - 1
endloop
// If there are none, proceed to the next loop
loop
exitwhen int <= 0
set bj_lastCreatedUnit = CreateUnit(feedback_player, RAW_ID(1), tx, ty, GetRandomReal(0, 360))
call SetUnitAcquireRange(bj_lastCreatedUnit, DATA(1, level))
call UnitAddAbility(bj_lastCreatedUnit, 'Aloc')
call Damage.add(bj_lastCreatedUnit)
call GroupAddUnit(spider_group, bj_lastCreatedUnit)
set int = int - 1
endloop
endif
endmethod
private method hive_addDetection takes nothing returns nothing
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive_addDetection: Adding a detection trigger.")
call line_Break()
endif
set detector = CreateTrigger()
call TriggerRegisterUnitInRange(detector, hive, DATA(1, level), null)
call TriggerAddCondition(detector, function thistype.hive_onDetectEnemy)
endmethod
private method hive_addFeedback takes nothing returns nothing
if not (DATA(6, level) != 0) then
call SetUnitInvulnerable(hive, false)
call UnitApplyTimedLife(hive, 'BTLF', mode_data)
call UnitPauseTimedLife(hive, false)
endif
call hive_addDetection()
call hive_spider_init()
call TimerStart(timer, DATA(2, level), true, function thistype.hive_onFeedback_loop)
endmethod
private method hive_addHighlight takes nothing returns nothing
local string s = MDL_FILE(5)
if IsPlayerEnemy(GetLocalPlayer(), GetOwningPlayer(unit)) then
set s = ""
endif
if mode == 2 and IsUnitSelected(unit, GetOwningPlayer(unit)) then
if hive_effect == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive_effect: Effect does not exist.")
call echo("thistype(" + I2S(this) + ").hive_effect: Creating.")
call line_Break()
endif
set hive_effect = AddSpecialEffectTarget(s, hive, "overhead")
endif
else
if hive_effect != null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive_effect: Effect exists.")
call echo("thistype(" + I2S(this) + ").hive_effect: Destroying.")
call line_Break()
endif
call DestroyEffect(hive_effect)
set hive_effect = null
endif
endif
endmethod
private static method hive_runList takes nothing returns nothing
local thistype this = thistype(0).active_next
if thistype(0).active_head == 0 then
call PauseTimer(ACTIVE_TIMER)
return
endif
loop
exitwhen this == 0
if mode == 1 then
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), GetUnitX(hive), GetUnitY(hive)))
static if REG_11_BOOL then
call SetUnitVertexColor(hive, 255, 255, 255, 255)
set mode = 2
call hive_addFeedback()
else
set mode_data = mode_data + (255)*DATA(10, 0)/DATA(11, 0)
if mode_data >= 255 then
call SetUnitVertexColor(hive, 255, 255, 255, 255)
set mode = 2
set mode_data = DATA(0, level)
call hive_addFeedback()
else
call SetUnitVertexColor(hive, 255, 255, 255, R2I(mode_data))
endif
endif
elseif mode == 2 or mode == 3 then
call hive_addHighlight()
set mode_data = mode_data - DATA(10, level)
elseif mode == 4 then
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), GetUnitX(unit), GetUnitY(unit)))
endif
set this = active_next
endloop
endmethod
private static method hive_spider_onDamage takes nothing returns nothing
local thistype this = get_group(Damage.source)
local real mode = ModuloReal(mode, 3.)
if mode == 0 then
set mode = 3
endif
if this != 0 then
if mode == 1 then
set Damage.amount = DATA(14, level)*GetHeroStr(unit, true)
elseif mode == 2 then
set Damage.amount = DATA(14, level)*GetHeroAgi(unit, true)
elseif mode == 3 then
set Damage.amount = DATA(14, level)*GetHeroInt(unit, true)
endif
endif
endmethod
// Part one
private static method hive_bufferAdd takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call UnitAddAbility(hive, 'Aloc')
call ReleaseTimer(GetExpiredTimer())
endmethod
static if LIBRARY_SelectionCircle then
private static method hive_whileSelected takes nothing returns nothing
local thistype this = SelectionCircle.current.data
call SelectionCircle.current.set_show_player(GetOwningPlayer(hive), IsUnitSelected(hive, GetOwningPlayer(hive)))
endmethod
endif
private method hive_create takes nothing returns nothing
if hive == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive: value is null")
call echo("thistype(" + I2S(this) + ").hive: Recreating the hive")
call line_Break()
endif
set hive = CreateUnit(GetOwningPlayer(unit), RAW_ID(0), tx, ty, 0)
set mode = 1
call SetUnitVertexColor(hive, 255, 255, 255, 0)
if DATA(6, level) != 0 then
call TimerStart(NewTimerEx(this), 0., false, function thistype.hive_bufferAdd)
else
call SetUnitInvulnerable(hive, true)
endif
else
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive: Reusing the recycled hive")
call line_Break()
endif
call SetUnitAnimation(hive, "stand")
call QueueUnitAnimation(hive, "stand")
call SetUnitInvulnerable(hive, false)
call SetUnitOwner(hive, GetOwningPlayer(unit), true)
call SetUnitPosition(hive, tx, ty)
call PauseUnit(hive, false)
call ShowUnit(hive, true)
if DATA(6, level) != 0 then
call UnitRemoveAbility(hive, 'Aloc')
call UnitAddAbility(hive, 'Aloc')
endif
set mode = 1
call SetUnitVertexColor(hive, 255, 255, 255, 0)
endif
if GetLocalPlayer() == GetOwningPlayer(unit) then
call SelectUnit(hive, true)
call SelectUnit(hive, false)
endif
static if LIBRARY_SelectionCircle then
set circle = SelectionCircle.attach_to_unit(hive, DATA(16, level), RAW_ID(3))
set circle.data = this
call circle.set_listener_event(function thistype.hive_whileSelected)
call circle.set_show(false)
set circle2 = SelectionCircle.attach_to_unit(hive, DATA(1, level), RAW_ID(5))
set circle2.data = this
call circle2.set_listener_event(function thistype.hive_whileSelected)
call circle2.set_show(false)
endif
call active_push()
set active_head = 1
if thistype(0).active_head == 0 then
call TimerStart(ACTIVE_TIMER, DATA(10, 0), true, function thistype.hive_runList)
endif
set thistype(0).active_head = thistype(0).active_head + 1
static if ADV_DEBUG then
call echo("thistype(0).active_head: Number of active instances is " + I2S(thistype(0).active_head))
call line_Break()
endif
endmethod
private static method prepare_hive_flag takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call hive_create()
endmethod
private method prepare_hive takes nothing returns nothing
set timer = NewTimerEx(this)
set timer2 = NewTimerEx(this)
call TimerStart(timer, real_GetDist(GetUnitX(unit), GetUnitY(unit), GetSpellTargetX(), GetSpellTargetY())/DATA(9, 0), false, function thistype.prepare_hive_flag)
endmethod
static method create takes unit u returns thistype
local thistype this = get(u)
local thistype that
if this == 0 then
set this = allocate()
set unit = u
static if ADV_DEBUG then
call echo("Instance of unit has been created.")
call line_Break()
endif
set level = GetUnitAbilityLevel(u, SPELL_ID())
set head = 1
call UnitAddAbility(u, RAW_ID(2))
call UnitMakeAbilityPermanent(u, true, RAW_ID(2))
call push()
call local_insert(this)
set tx = GetSpellTargetX()
set ty = GetSpellTargetY()
call prepare_hive()
return this
endif
set that = allocate()
set that.unit = unit
static if ADV_DEBUG then
call echo("Allocated another instance.")
call line_Break()
endif
set that.level = GetUnitAbilityLevel(unit, SPELL_ID())
call that.local_insert(this)
set that.tx = GetSpellTargetX()
set that.ty = GetSpellTargetY()
call UnitAddAbility(u, RAW_ID(2))
call UnitMakeAbilityPermanent(u, true, RAW_ID(2))
call that.prepare_hive()
return that
endmethod
static method teleport takes unit u, real x, real y returns nothing
local thistype this = get(u)
local thistype head = this
local thistype nearest = head
if this == 0 then
call echo("thistype.teleport: Instance of unit does not exist.")
call echo("thistype.teleport: Returning")
return
endif
set this = local_next
loop
exitwhen this == head
if (real_GetDist(x, y, GetUnitX(hive), GetUnitY(hive)) <= real_GetDist(x, y, GetUnitX(nearest.hive), GetUnitY(nearest.hive))) and mode == 2 then
set nearest = this
endif
set this = local_next
endloop
if nearest.mode != 2 then
call DisplayTextToPlayer(GetOwningPlayer(u), 0, 0, "|cffffcc00The hives are not available.|r")
call IssueImmediateOrderById(u, 851972)
else
call nearest.overmind_delayTeleport()
//call nearest.overmind_onTeleport()
endif
endmethod
// Init method
public static method init takes nothing returns nothing
local trigger t = CreateTrigger()
set target_search_code = function thistype.onTarget_search
set hive_onFeedback_loop_code = function thistype.hive_onFeedback_loop
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, function thistype.hive_onDeathEvent)
call TriggerAddCondition(t, function thistype.target_onDeathEvent)
set t = CreateTrigger()
call Damage.registerModifierTrigger(t)
call TriggerAddCondition(t, function thistype.hive_spider_onDamage)
// Located at part two
set t = null
endmethod
endstruct
private function OnSpellEvent takes nothing returns nothing
if GetSpellAbilityId() == SPELL_ID() then
call Data.create(GetTriggerUnit())
elseif GetSpellAbilityId() == RAW_ID(2) then
call Data.teleport(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function OnSpellEvent)
call ForForce(bj_FORCE_PLAYER[0], function Data.init)
set t = null
endfunction
private struct InitS extends array
private static method onInit takes nothing returns nothing
call Init()
endmethod
endstruct
//! textmacro search_list takes E, M, T, IT, C
$E$ static method $M$ takes $T$ id returns thistype
local thistype this = thistype(0).$IT$
loop
exitwhen this == 0 or $C$
set this = $IT$
endloop
return this
endmethod
//! endtextmacro
endscope
endlibrary
library OvermindSpellThree initializer Init requires /*
* ----------------------
*/ AllocationAndLinks, /* https://www.hiveworkshop.com/threads/allocation-and-links.293621/
* ----------------------
*
* - Contains the doubly linked list and custom allocation which is preferred by the coder.
* - If you don't want extends array syntax, just change AllocLinkBundle to DoubleLink
*
* ----------------------
*/ TimerUtils, /* http://www.wc3c.net/showthread.php?t=101322
* ----------------------
*
* - Too lazy to create a timer library that allows attachment of data,
* but not too lazy to completely rewrite it.
*
* ----------------------------
*/ /*
* ----------------------------
*
* - Allows a nice not-red debug setting, and allows the instant generation of a search function.
* Technically not useless.
*
* --------------------------------
*/ DamageEvent, DamageModify, /* https://www.hiveworkshop.com/threads/damagepackage.287101/
* --------------------------------
*
* - Allows the manipulation of damage
* - DamageModify is the takeaway here. (Modifies the damage dealt by the spiderlings).
*
* --------------------------------
*/ optional TextTag /* https://www.hiveworkshop.com/threads/library-text-tag.290924/page-2#post-3138916
* --------------------------------
* - Optionally displays the amount restored to the hive creator.
* - Can be used informationally.
*
* --------------------------------
*/ optional SelectionCircle /*
* --------------------------------
* - Creates a circle which shows the range of something.
* Also optional, is used by the library as an interface add-on.
*
* Requirement end:
*/
/*
* __________________________
*
* OvermindSpellThree
* __________________________
*
* ______________
*
* The Hive
* ______________
*
* - A spell based on Pocket Factory that plants a targeted location with a hive mind.
* - This spell is active, and has a sub-spell component, namely Ravage Hive.
*
* __________________
*
* How it goes
* __________________
*
* - The spell is cast, triggering a spell-effect trigger and creating an associated
* struct instance of type Data.
*
* - The Data struct instance then activates a timer, which is set to expire based on
* the speed of the missile created by the ability and the distance therefrom.
*
* - After expiration, the hive is created with a delay in its' timed life. During this
* time, the hive is rendered invulnerable.
*
* - After the delay, the expiration timer for the hive is started. The hive may or may
* not be harmed based on the data flag section.
*
* - If it dies, its' expiration timer expires, or a Ravage Hive sub-spell has taken effect,
* the hive will execute its' death-related event. This may bug out with certain scripts,
* which remove units on death.
*
* ____________
*
* API
* ____________
*
* native functions:
* - UnitAlive(unit id) returns boolean
*
* external functions:
* - external func:
* exe: ObjectMerger.exe
* type: w3u / units
* base id: 'hfoo' / footman
* new id: '!000' / footman derivative.
*
* model: "dummy.mdx"
*
* globals:
* private constant group ENUM_GROUP
* - Enumerator group
* private unit enum_unit
* - Enumerator unit variable.
* private constant boolean ADV_DEBUG
* - Determines whether or not to display detailed debugging.
*
* configurable functions:
* function SPELL_ID() returns integer
* - Returns the raw code of the spell. (See in Object Editor (Ctrl + D))
* function RAW_ID(int i) returns integer
* - Returns some raw code based on request. (Defaults to 0)
* function DATA(int i, int lev) returns real
* - Returns the necessary soft-coded data field. (Defaults to 0)
*
* The library also allows debug messages to be printed while in DEBUG_MODE for the
* convenience of the spell importer. It even has an option for advanced debugging
* if toggled to true.
*
* External scripts:
* - Used mostly for the generation of the spiderling with the added speed properties.
* Can be modified to a certain extent by script.
*
*/
native UnitAlive takes unit id returns boolean
/* The following scripts are external lua scripts which generate the necessary unit data.
* Since I wanted to make this configurable (to a certain extent), the script for declaring
* a spider is given below.
*
* This line of code has been deemed redundant due to the pathing script being removed.
* ___________________________________________________________
*
* ///! external ObjectMerger w3u hfoo "!000" umdl "dummy.mdx"
* ___________________________________________________________
*
* The code below may be commented out by adding a delimited comment.
* Delimited comments look like this:
*
* /*
* Put some stuff here.
* */
*
* To enable the script again, just remove the delimiters.
*
* ___________
*
* API
* ___________
*
* ID = "char"
* - The rawcode of the spider.
*
* ATTACK_SPEED = float r
* - The attack speed defined as a float.
* MOVE_SPEED = float r
* - The movement speed of the spider defined as an integer.
* NAME = string nam
* - The name of the spider. If you like, you can change it.
*
* function declare_spider(real atkSpeed, int mvSpeed, string paramName)
* - Due to the lack of explicit type definition, the type of the parameters have been
* supplied for you.
*
* - Generates a spider with a certain raw id based on the ID variable (string)
* - The only configurable parts are the attack speed, move speed and name
* Even then, the attack speed is capped off at 5.0 and 0.1.
*
* - Will cause the Typecasting library to fail if the external script is not delimited.
*/
/*
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i ID = "e002"
//! i ATTACK_SPEED = 1.0
//! i MOVE_SPEED = 500
//! i NAME = "Fel Spiderling"
//! i function declare_spider(atkSpeed, mvSpeed, paramName)
//! i setobjecttype("units")
//! i createobject("nspd", ID)
//! i makechange(current, "uico", "ReplaceableTextures\\CommandButtons\\BTNSpiderBlack.blp")
//! i makechange(current, "umdl", "units\\creeps\\SpiderBlack\\SpiderBlack.mdl")
//! i makechange(current, "usca", 0.4)
//! i makechange(current, "ussc", 0.9)
//! i makechange(current, "ushx", 15)
//! i makechange(current, "ushy", 15)
//! i makechange(current, "ushh", 40)
//! i makechange(current, "ushw", 40)
//! i makechange(current, "ua1t", "chaos")
//! i makechange(current, "ua1c", math.min(math.max(atkSpeed, 0.1), 5.0))
//! i makechange(current, "ua1b", 0)
//! i makechange(current, "ua1s", 1)
//! i makechange(current, "ua1d", 1)
//! i makechange(current, "ua1r", 90)
//! i makechange(current, "umvs", math.min(mvSpeed, 522))
//! i makechange(current, "umas", math.min(mvSpeed, 522))
//! i makechange(current, "umis", math.min(mvSpeed - 1, 100))
//! i makechange(current, "uhpm", 250)
//! i makechange(current, "urac", "undead")
//! i makechange(current, "unam", paramName)
//! i makechange(current, "utip", "Summon " .. paramName)
//! i makechange(current, "utub", "Light melee unit. Deals damage based on attribute. |n|n|cffffcc00Attacks land units.|r")
//! i end
//! i declare_spider(ATTACK_SPEED, MOVE_SPEED, NAME)
//! endexternalblock
*/
globals
private constant group ENUM_GROUP = CreateGroup()
private constant boolean ADV_DEBUG = false
private unit enum_unit = null
endglobals
private constant function SPELL_ID takes nothing returns integer
return 'A002'
endfunction
private constant function RAW_ID takes integer request returns integer
if request == 0 then
// Returns the ID of the hatchery.
return 'e001'
elseif request == 1 then
// Returns the ID of the fel spiderlings
return 'e002'
elseif request == 2 then
// Returns the ID of the sub-spell
return '1001'
elseif request == 3 then
// Returns the hex value of the selection circle.
// Used by the selection Circle
// Goes by Red, Green, Blue, and Alpha values (with maximum for each being 0xFF)
return 0xFFFFFFFF
elseif request == 4 then
// Returns the hex value of another selection circle. (on Death)
return 0xFF0000FF
elseif request == 5 then
// Returns a dotted green selection circle. (Displays target acquisition)
return 0x00FF00FF
endif
return 0
endfunction
private constant function DATA takes integer request, integer level returns real
if request == 0 then
// Returns the duration of the hive
if level == 1 then
return 50.
elseif level == 2 then
return 75.
elseif level == 3 then
return 100.
endif
elseif request == 1 then
// Returns the effective detection range.
if level == 1 then
return 150.
elseif level == 2 then
return 250.
elseif level == 3 then
return 350.
endif
elseif request == 2 then
// Returns the tick rate.
// Defaults to 1.
return 1.
elseif request == 3 then
// Returns the amount of mana burned around the hive
if level == 1 then
return 5.
elseif level == 2 then
return 7.
elseif level == 3 then
return 10.
endif
elseif request == 4 then
// Returns the amount of health rejuvenated by ratio.
// Only takes effect when used as a nydus canal.
if level == 1 then
return 0.1
elseif level == 2 then
return 0.12
elseif level == 3 then
return 0.16
endif
elseif request == 5 then
// Returns the range field for allowing mana burn
// Defaults to 500.
return 500.
elseif request == 6 then
// Returns the flag for allowing the hive to be invulnerable.
// Defaults to true (not equal to 0.)
return 0.
elseif request == 7 then
// Returns the damage dealt when hive (simulating death or actually dying)
if level == 1 then
return 85.
elseif level == 2 then
return 150.
elseif level == 3 then
return 220.
endif
elseif request == 8 then
// Returns the amount of spiders that will spawn.
if level == 1 then
return 4.
elseif level == 2 then
return 5.
elseif level == 3 then
return 6.
endif
elseif request == 9 then
// Returns the speed of the missile
// Must correspond to the object data equivalent.
return 1000.
elseif request == 10 then
// Returns the frequency of the global timer
// Cannot be configured to the level of the unit
return 1/32.
elseif request == 11 then
// Returns the fade-in total duration.
// Cannot be configured to the level of the unit
return 2.5
elseif request == 12 then
// Returns the duration of the death animation
// Cannot be configured to the level of the unit
return 5.
elseif request == 13 then
// Returns the main attribute to follow
// Higher values will return modulo of 3.
// Strength: 1
// Agility: 2
// Intelligence: 3
return 3.
elseif request == 14 then
// Returns damage dealt based on percentage
if level == 1 then
return 0.1
elseif level == 2 then
return 0.14
elseif level == 3 then
return 0.18
endif
elseif request == 15 then
// Returns the amount of mana rejuvenated by ratio.
// Only takes effect when used as a nydus canal.
if level == 1 then
return 0.12
elseif level == 2 then
return 0.16
elseif level == 3 then
return 0.2
endif
elseif request == 16 then
// Returns the distance offset of a targeted unit.
if level == 1 then
return DATA(1, level) + 150.
elseif level == 2 then
return DATA(1, level) + 175.
elseif level == 3 then
return DATA(1, level) + 200.
endif
elseif request == 17 then
// Returns the distance check for notifying the player of a potential target.
// Cannot be configured to the level of the unit.
return 3000.
elseif request == 18 then
// Returns the time delay in updating the text warnings.
// Cannot be configured to the level of the unit.
return 15.
elseif request == 19 then
// Returns the amount of time to display a selection circle on the eruption of a hive.
// Cannot be configured to the level of the unit.
return 0.2
elseif request == 20 then
// Returns the amount of time (delay) for the teleportation of the unit.
// Cannot be configured to the level of the unit.
return 2.5
endif
return 0.
endfunction
/* Generated boolean comparison (inlined) */
globals
private constant boolean REG_11_BOOL = 2.5 <= 0.
endglobals
/* The strings which will be used for projecting certain effects and such */
private constant function MDL_FILE takes integer request returns string
if request == 0 then
// Chimaera Acid
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif request == 1 then
// Mana burn effect
return "Abilities\\Spells\\NightElf\\ManaBurn\\ManaBurnTarget.mdl"
elseif request == 2 then
// Snap missile (Virtually useless)
return "Abilities\\Weapons\\snapMissile\\snapMissile.mdl"
elseif request == 3 then
// Detonate effect
return "Units\\NightElf\\Wisp\\WispExplode.mdl"
elseif request == 4 then
// Talk to me sign, denotes target of the hive.
return "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"
elseif request == 5 then
// Highlights the hives that are generated by the Fel spider
return "Abilities\\Spells\\Other\\Parasite\\ParasiteTarget.mdl"
elseif request == 6 then
// Special effect to be played when teleported.
return "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
elseif request == 7 then
// Special effect to be played when teleporting.
return "Abilities\\Spells\\Undead\\DeathPact\\DeathPactTarget.mdl"
elseif request == 8 then
// Special effect to be played as some sort of eruption effect.
return MDL_FILE(6)
endif
return ""
endfunction
private function real_GetDist takes real x1, real y1, real x2, real y2 returns real
return SquareRoot((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2))
endfunction
private function echo takes string s returns nothing
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 16, s)
endfunction
private function line_Break takes nothing returns nothing
call echo(" ")
endfunction
// Generates a module with the name DoubleLink_local. It has the property of a private module.
// Same with the next textmacro. A private module DoubleLink_active is generated.
//! runtextmacro DLinkedListT("local", "private")
//! runtextmacro DLinkedListT("active", "private")
private struct Data extends array
implement AllocLinkBundle
implement DoubleLink_local
implement DoubleLink_active
private static constant timer ACTIVE_TIMER = CreateTimer()
private static constant group STORED_SPIDERS = CreateGroup()
// Assistant to the stored spider group
private static integer stored_spider_count = 0
// Counter for a forGroupEx call
private static integer spider_group_count = 0
// Mode checker
private integer mode
private real mode_data
/* Part One
* - This will store the unit, level, timer, target x, and target y for
* the successful creation of the hive. */
private unit unit
private unit hive
private integer level
private timer timer2
private timer timer
private real tx
private real ty
private effect hive_effect
// Part Two
/* - This will be the main part of the spell, the mechanics of which will
* be the main focus and intention of this spell. */
// For speed purposes
private static player feedback_player
private static thistype hive_this
private static thistype spider_this
private real stored
private integer eventtype
private unit target
private trigger detector
private group spider_group
private effect head_effect
static if LIBRARY_SelectionCircle then
private SelectionCircle circle
private SelectionCircle circle2
endif
/*
These textmacros can be found at CustomFunctions. It's easy to port them to this library, though.
They generate a searching algorithm for finding a certain instance.
If not fully explained, the parameters are defined as follows:
search_list(encapsulation-keyword, name, searched type, iterator variable, conditional statement)
*/
//! runtextmacro search_list("private", "get", "unit", "next", "unit == id")
//! runtextmacro search_list("private", "get_hive", "unit", "active_next", "hive == id")
//! runtextmacro search_list("private", "get_targ", "unit", "active_next", "target == id")
//! runtextmacro search_list("private", "get_trig", "trigger", "active_next", "detector == id")
//! runtextmacro search_list("private", "get_group", "unit", "active_next", "IsUnitInGroup(id, spider_group)")
// Detaches a certain instance from a global list if it is found and in the local list.
// Pushes the next local instance to the global list if there exists at least one more.
private method detach takes nothing returns nothing
if head != 0 then
if local_next != this then
set local_next.head = 1
call local_next.insert(next)
endif
set head = 0
call pop()
endif
call local_pop()
endmethod
/* Mimics death for the spiders */
private static method spider_preDestroy_enum takes nothing returns nothing
set enum_unit = GetEnumUnit()
call PauseUnit(enum_unit, true)
call SetUnitAnimation(enum_unit, "death")
endmethod
private method spider_preDestroy takes nothing returns nothing
call ForGroup(spider_group, function thistype.spider_preDestroy_enum)
set enum_unit = null
endmethod
/* Resets the animation for the next group */
private static method spider_onDestroy_enum takes nothing returns nothing
set enum_unit = GetEnumUnit()
call QueueUnitAnimation(enum_unit, "stand")
call SetUnitX(enum_unit, 0)
call SetUnitY(enum_unit, 0)
call SetUnitOwner(enum_unit, Player(15), true)
call ShowUnit(enum_unit, false)
endmethod
private method spider_onDestroy takes nothing returns nothing
call ForGroup(spider_group, function thistype.spider_onDestroy_enum)
set enum_unit = null
endmethod
/* Check if the hive is still alive. If not, attempt to nullify hive and prepare a new one. */
private method preDestroy takes nothing returns nothing
if not UnitAlive(hive) then
call RemoveUnit(hive)
set hive = null
else
call PauseUnit(hive, true)
call ShowUnit(hive, false)
call SetUnitX(hive, 0)
call SetUnitY(hive, 0)
call SetWidgetLife(hive, GetUnitState(hive, UNIT_STATE_MAX_LIFE))
call SetUnitOwner(hive, Player(15), false)
if not (DATA(6, level) != 0) then
call UnitRemoveAbility(hive, 'Aloc')
endif
endif
endmethod
private method destroy takes nothing returns nothing
static if ADV_DEBUG then
call echo("Deallocating the instance: thistype(" + I2S(this) + ")")
endif
call spider_onDestroy()
call preDestroy()
call ReleaseTimer(timer)
call ReleaseTimer(timer2)
call DestroyTrigger(detector)
if GetHandleId(head_effect) != 0 then
call echo("Instance member error: Effect has not yet been destroyed!")
call echo("Please notify the spell creator immediately!")
call line_Break()
call DestroyEffect(head_effect)
endif
debug if GetUnitAbilityLevel(unit, RAW_ID(2)) != 0 and local_next == this and get(unit) == 0 then
debug call echo("Ability error: Ability has not been removed!")
debug call echo("Please notify the spell creator immediately!")
debug call line_Break()
debug endif
set unit = null
set target = null
set timer2 = null
set timer = null
set detector = null
set head_effect = null
set tx = 0.
set ty = 0.
set mode_data = 0.
set stored = 0.
static if LIBRARY_SelectionCircle then
set circle = 0
set circle2 = 0
endif
set head = 0
set mode = 0
set eventtype = 0
call active_pop()
set active_head = 0
set thistype(0).active_head = thistype(0).active_head - 1
call deallocate()
endmethod
// Part two
private static code target_search_code = null
private static method target_attack_enum takes nothing returns nothing
local thistype this = spider_this
set enum_unit = GetEnumUnit()
if target != null then
if UnitAlive(target) then
call IssueTargetOrder(enum_unit, "attack", target)
else
if GetHandleId(head_effect) != 0 then
call DestroyEffect(head_effect)
endif
set target = null
endif
else
call IssuePointOrder(enum_unit, "move", GetUnitX(hive) + GetRandomReal(-DATA(1, level), DATA(1, level)), GetUnitY(hive) + GetRandomReal(-DATA(1, level), DATA(1, level)))
endif
endmethod
private method target_attack takes nothing returns nothing
local real rx = GetUnitX(hive)
local real ry = GetUnitY(hive)
set spider_this = this
if (target != null and real_GetDist(rx, ry, GetUnitX(target), GetUnitY(target)) > DATA(16, level)) then
call ForForce(bj_FORCE_PLAYER[0], target_search_code)
endif
call ForGroup(spider_group, function thistype.target_attack_enum)
set spider_this = 0
endmethod
private method target_search_onFinish takes nothing returns nothing
local real rx = GetUnitX(target)
local real ry = GetUnitY(target)
if GetLocalPlayer() == GetOwningPlayer(hive) then
if real_GetDist(GetCameraTargetPositionX(), GetCameraTargetPositionY(), GetUnitX(hive), GetUnitY(hive)) > DATA(17, 0) then
call PingMinimapEx(rx, ry, 3., 255, 0, 0, true)
call DisplayTextToPlayer(GetOwningPlayer(hive), -0.5, 0, "|cffffcc00A target has been found!")
endif
endif
endmethod
private method target_search takes nothing returns nothing
set feedback_player = GetOwningPlayer(hive)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(hive), GetUnitY(hive), DATA(1, level), null)
call GroupRemoveUnit(ENUM_GROUP, target)
call DestroyEffect(head_effect)
set head_effect = null
set target = null
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
if UnitAlive(enum_unit) and IsUnitEnemy(enum_unit, feedback_player) then
if UnitDamageTarget(enum_unit, enum_unit, 0, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null) then
static if ADV_DEBUG then
call echo("ENUM_GROUP: Current enum_unit is a viable target!")
call line_Break()
endif
set target = enum_unit
exitwhen true
else
static if ADV_DEBUG then
call echo("ENUM_GROUP: Current enum_unit is not a viable target!")
call line_Break()
endif
endif
endif
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
if enum_unit == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").target_search: No targets in range.")
call echo("thistype(" + I2S(this) + ").target_search: Defaulting to idle spiders.")
call line_Break()
endif
set target = null
else
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").target_search: A target was discovered.")
call line_Break()
endif
set head_effect = AddSpecialEffectTarget(MDL_FILE(4), target, "overhead")
if not (TimerGetRemaining(timer2) != 0) then
call target_search_onFinish()
call TimerStart(timer2, DATA(18, 0), false, null)
endif
endif
endmethod
private static method onTarget_search takes nothing returns nothing
call spider_this.target_search()
endmethod
private static method target_onDeathEvent takes nothing returns nothing
local thistype this = get_targ(GetTriggerUnit())
local thistype that = this.local_next
loop
exitwhen that == this
if that.target == target then
call that.target_search()
call that.target_attack()
endif
set that = that.local_next
endloop
if this != 0 and mode != 3 then
call target_search()
call target_attack()
endif
endmethod
private static method hive_onDeathProxy takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call destroy()
endmethod
static if LIBRARY_SelectionCircle then
private static method selectionCircle_onExpire takes nothing returns nothing
local SelectionCircle sc = GetTimerData(GetExpiredTimer())
call sc.destroy()
endmethod
endif
private static method hive_onErupt takes nothing returns nothing
local thistype this = hive_this
static if LIBRARY_SelectionCircle then
local SelectionCircle sc = SelectionCircle.create(GetUnitX(hive), GetUnitY(hive), DATA(5, level), RAW_ID(4))
endif
set hive_this = 0
set feedback_player = GetOwningPlayer(hive)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(hive), GetUnitY(hive), DATA(5, level), null)
static if LIBRARY_SelectionCircle then
call SetTimerData(timer2, sc)
call TimerStart(timer2, DATA(19, 0), false, function thistype.selectionCircle_onExpire)
endif
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
if UnitAlive(enum_unit) and IsUnitEnemy(enum_unit, feedback_player) then
if UnitDamageTarget(hive, enum_unit, DATA(7, level), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null) then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(6), enum_unit, "chest"))
endif
endif
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
endmethod
private method hive_onDeath takes nothing returns nothing
call PauseTimer(timer)
call PauseTimer(timer2)
call TimerStart(timer, DATA(12, 0), false, function thistype.hive_onDeathProxy)
set mode = 3
if eventtype != 1 then
call SetUnitAnimation(hive, "death")
endif
if UnitAlive(target) then
call DestroyEffect(head_effect)
set head_effect = null
endif
static if LIBRARY_SelectionCircle then
call circle.destroy()
call circle2.destroy()
endif
if local_next == this then
call UnitRemoveAbility(unit, RAW_ID(2))
endif
call detach()
set hive_this = this
call ForForce(bj_FORCE_PLAYER[0], function thistype.hive_onErupt)
call spider_preDestroy()
endmethod
private method overmind_onTeleport takes nothing returns nothing
local real rx = 0.
local real ry = 0.
local player p = GetOwningPlayer(unit)
if this == 0 then
call echo("thistype.overmind_onTeleport: Invalid instance!")
return
endif
set rx = GetUnitX(unit)
set ry = GetUnitY(unit)
call SetWidgetLife(unit, GetWidgetLife(unit) + stored*DATA(4, level))
call SetUnitState(unit, UNIT_STATE_MANA, GetUnitState(unit, UNIT_STATE_MANA) + stored*DATA(15, level))
// For boolean consistency
set eventtype = 3
if not (DATA(6, level) != 0) then
call UnitRemoveBuffsEx(hive, true, true, false, false, true, false, false)
endif
call DestroyEffect(AddSpecialEffect(MDL_FILE(3), rx, ry))
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), rx, ry))
call DestroyEffect(AddSpecialEffect(MDL_FILE(8), GetUnitX(hive), GetUnitY(hive)))
call SetUnitX(unit, GetUnitX(hive))
call SetUnitY(unit, GetUnitY(hive))
static if LIBRARY_TextTag then
if not (stored != 0.) then
call CreateTextTagBJ(p, GetUnitX(hive), GetUnitY(hive), 75, 0, 255, 255, "+" + I2S(R2I(stored*(DATA(4, level)))) + " health!\n+" + I2S(R2I(stored*(DATA(15, level)))) + " mana!")
set vj_lastCreatedTextTag.duration = 3.0
set vj_lastCreatedTextTag.fade = 2.25
endif
endif
// Unimportant to deallocate players (hear-say)
endmethod
private static method overmind_onDelayFinish takes nothing returns nothing
local thistype this = ReleaseTimer(GetExpiredTimer())
call SetUnitInvulnerable(unit, false)
if UnitRemoveAbility(unit, RAW_ID(2)) then
call UnitAddAbility(unit, RAW_ID(2))
call SetUnitAbilityLevel(unit, RAW_ID(2), level)
endif
if mode != 3 then
call IssueImmediateOrderById(unit, 851972)
call overmind_onTeleport()
debug else
static if ADV_DEBUG then
call echo("The hive must have been killed!")
endif
endif
endmethod
private static code hive_onFeedback_loop_code
private method overmind_delayTeleport takes nothing returns nothing
set mode = 4
call PauseTimer(timer)
call TimerStart(timer, DATA(2, level), true, hive_onFeedback_loop_code)
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(7), hive, "overhead"))
call SetUnitInvulnerable(unit, true)
call TimerStart(NewTimerEx(this), RMinBJ(DATA(20, 0), mode_data - DATA(10, 0)), false, function thistype.overmind_onDelayFinish)
endmethod
private static method hive_onDeathEvent takes nothing returns nothing
local thistype this = get_hive(GetTriggerUnit())
if this != 0 then
if eventtype == 0 then
set eventtype = 1
endif
call hive_onDeath()
endif
endmethod
private static method hive_onFeedback_loop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if mode == 2 then
if mode_data > 0. then
call target_attack()
set feedback_player = GetOwningPlayer(hive)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(hive), GetUnitY(hive), DATA(5, level), null)
loop
set enum_unit = FirstOfGroup(ENUM_GROUP)
exitwhen enum_unit == null
if UnitAlive(enum_unit) and IsUnitEnemy(enum_unit, feedback_player) then
if GetUnitState(enum_unit, UNIT_STATE_MAX_MANA) != 0 then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(1), enum_unit, "chest"))
call SetUnitState(enum_unit, UNIT_STATE_MANA, GetUnitState(enum_unit, UNIT_STATE_MANA) - DATA(3, level))
call UnitDamageTarget(hive, enum_unit, DATA(3, level), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
set stored = stored + DATA(3, level)
endif
endif
call GroupRemoveUnit(ENUM_GROUP, enum_unit)
endloop
else
set eventtype = 2
call hive_onDeath()
endif
elseif mode == 4 then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(7), hive, "overhead"))
set mode_data = mode_data - DATA(2, level)
endif
endmethod
private static method hive_onDetectEnemy takes nothing returns nothing
local thistype this = get_trig(GetTriggeringTrigger())
if IsUnitEnemy(GetTriggerUnit(), GetOwningPlayer(hive)) then
if target == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").target: An appropriate target has been found!")
call line_Break()
endif
set target = GetTriggerUnit()
if head_effect != null then
call DestroyEffect(head_effect)
endif
set head_effect = AddSpecialEffectTarget(MDL_FILE(4), target, "overhead")
call target_attack()
endif
endif
endmethod
private static method spider_forGroupEx_enum takes nothing returns nothing
local thistype this = spider_this
set enum_unit = GetEnumUnit()
set spider_group_count = spider_group_count + 1
call SetUnitOwner(enum_unit, feedback_player, true)
call SetUnitPosition(enum_unit, tx, ty)
call SetUnitFacing(enum_unit, GetRandomReal(0, 360))
call PauseUnit(enum_unit, false)
call ShowUnit(enum_unit, true)
call UnitRemoveAbility(enum_unit, 'Aloc')
call UnitAddAbility(enum_unit, 'Aloc')
endmethod
private method spider_forGroupEx takes nothing returns integer
set spider_this = this
set spider_group_count = 0
call ForGroup(spider_group, function thistype.spider_forGroupEx_enum)
set enum_unit = null
return spider_group_count
endmethod
private method hive_spider_init takes nothing returns nothing
local integer int = R2I(DATA(8, level))
set feedback_player = GetOwningPlayer(unit)
if spider_group == null then
set spider_group = CreateGroup()
endif
set int = int - spider_forGroupEx()
if int < 0 then
loop
set enum_unit = FirstOfGroup(spider_group)
exitwhen int >= 0
call SetUnitOwner(enum_unit, Player(15), true)
call SetUnitX(enum_unit, 0)
call SetUnitY(enum_unit, 0)
call PauseUnit(enum_unit, true)
call ShowUnit(enum_unit, false)
call UnitRemoveAbility(enum_unit, 'Aloc')
call UnitAddAbility(enum_unit, 'Aloc')
call GroupRemoveUnit(spider_group, enum_unit)
call GroupAddUnit(STORED_SPIDERS, enum_unit)
set stored_spider_count = stored_spider_count + 1
set int = int + 1
endloop
else
// We check if there are any free spiders in the group.
loop
set enum_unit = FirstOfGroup(STORED_SPIDERS)
exitwhen enum_unit == null or int <= 0 or stored_spider_count <= 0
call SetUnitOwner(enum_unit, feedback_player, true)
call SetUnitPosition(enum_unit, tx, ty)
call SetUnitFacing(enum_unit, GetRandomReal(0, 360))
call PauseUnit(enum_unit, false)
call ShowUnit(enum_unit, true)
call UnitRemoveAbility(enum_unit, 'Aloc')
call UnitAddAbility(enum_unit, 'Aloc')
call GroupRemoveUnit(STORED_SPIDERS, enum_unit)
call GroupAddUnit(spider_group, enum_unit)
set stored_spider_count = stored_spider_count - 1
set int = int - 1
endloop
// If there are none, proceed to the next loop
loop
exitwhen int <= 0
set bj_lastCreatedUnit = CreateUnit(feedback_player, RAW_ID(1), tx, ty, GetRandomReal(0, 360))
call SetUnitAcquireRange(bj_lastCreatedUnit, DATA(1, level))
call UnitAddAbility(bj_lastCreatedUnit, 'Aloc')
call Damage.add(bj_lastCreatedUnit)
call GroupAddUnit(spider_group, bj_lastCreatedUnit)
set int = int - 1
endloop
endif
endmethod
private method hive_addDetection takes nothing returns nothing
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive_addDetection: Adding a detection trigger.")
call line_Break()
endif
set detector = CreateTrigger()
call TriggerRegisterUnitInRange(detector, hive, DATA(1, level), null)
call TriggerAddCondition(detector, function thistype.hive_onDetectEnemy)
endmethod
private method hive_addFeedback takes nothing returns nothing
if not (DATA(6, level) != 0) then
call SetUnitInvulnerable(hive, false)
call UnitApplyTimedLife(hive, 'BTLF', mode_data)
call UnitPauseTimedLife(hive, false)
endif
call hive_addDetection()
call hive_spider_init()
call TimerStart(timer, DATA(2, level), true, function thistype.hive_onFeedback_loop)
endmethod
private method hive_addHighlight takes nothing returns nothing
local string s = MDL_FILE(5)
if IsPlayerEnemy(GetLocalPlayer(), GetOwningPlayer(unit)) then
set s = ""
endif
if mode == 2 and IsUnitSelected(unit, GetOwningPlayer(unit)) then
if hive_effect == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive_effect: Effect does not exist.")
call echo("thistype(" + I2S(this) + ").hive_effect: Creating.")
call line_Break()
endif
set hive_effect = AddSpecialEffectTarget(s, hive, "overhead")
endif
else
if hive_effect != null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive_effect: Effect exists.")
call echo("thistype(" + I2S(this) + ").hive_effect: Destroying.")
call line_Break()
endif
call DestroyEffect(hive_effect)
set hive_effect = null
endif
endif
endmethod
private static method hive_runList takes nothing returns nothing
local thistype this = thistype(0).active_next
if thistype(0).active_head == 0 then
call PauseTimer(ACTIVE_TIMER)
return
endif
loop
exitwhen this == 0
if mode == 1 then
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), GetUnitX(hive), GetUnitY(hive)))
static if REG_11_BOOL then
call SetUnitVertexColor(hive, 255, 255, 255, 255)
set mode = 2
call hive_addFeedback()
else
set mode_data = mode_data + (255)*DATA(10, 0)/DATA(11, 0)
if mode_data >= 255 then
call SetUnitVertexColor(hive, 255, 255, 255, 255)
set mode = 2
set mode_data = DATA(0, level)
call hive_addFeedback()
else
call SetUnitVertexColor(hive, 255, 255, 255, R2I(mode_data))
endif
endif
elseif mode == 2 or mode == 3 then
call hive_addHighlight()
set mode_data = mode_data - DATA(10, level)
elseif mode == 4 then
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), GetUnitX(unit), GetUnitY(unit)))
endif
set this = active_next
endloop
endmethod
private static method hive_spider_onDamage takes nothing returns nothing
local thistype this = get_group(Damage.source)
local real mode = ModuloReal(mode, 3.)
if mode == 0 then
set mode = 3
endif
if this != 0 then
if mode == 1 then
set Damage.amount = DATA(14, level)*GetHeroStr(unit, true)
elseif mode == 2 then
set Damage.amount = DATA(14, level)*GetHeroAgi(unit, true)
elseif mode == 3 then
set Damage.amount = DATA(14, level)*GetHeroInt(unit, true)
endif
endif
endmethod
// Part one
private static method hive_bufferAdd takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call UnitAddAbility(hive, 'Aloc')
call ReleaseTimer(GetExpiredTimer())
endmethod
static if LIBRARY_SelectionCircle then
private static method hive_whileSelected takes nothing returns nothing
local thistype this = SelectionCircle.current.data
call SelectionCircle.current.set_show_player(GetOwningPlayer(hive), IsUnitSelected(hive, GetOwningPlayer(hive)))
endmethod
endif
private method hive_create takes nothing returns nothing
if hive == null then
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive: value is null")
call echo("thistype(" + I2S(this) + ").hive: Recreating the hive")
call line_Break()
endif
set hive = CreateUnit(GetOwningPlayer(unit), RAW_ID(0), tx, ty, 0)
set mode = 1
call SetUnitVertexColor(hive, 255, 255, 255, 0)
if DATA(6, level) != 0 then
call TimerStart(NewTimerEx(this), 0., false, function thistype.hive_bufferAdd)
else
call SetUnitInvulnerable(hive, true)
endif
else
static if ADV_DEBUG then
call echo("thistype(" + I2S(this) + ").hive: Reusing the recycled hive")
call line_Break()
endif
call SetUnitAnimation(hive, "stand")
call QueueUnitAnimation(hive, "stand")
call SetUnitInvulnerable(hive, false)
call SetUnitOwner(hive, GetOwningPlayer(unit), true)
call SetUnitPosition(hive, tx, ty)
call PauseUnit(hive, false)
call ShowUnit(hive, true)
if DATA(6, level) != 0 then
call UnitRemoveAbility(hive, 'Aloc')
call UnitAddAbility(hive, 'Aloc')
endif
set mode = 1
call SetUnitVertexColor(hive, 255, 255, 255, 0)
endif
if GetLocalPlayer() == GetOwningPlayer(unit) then
call SelectUnit(hive, true)
call SelectUnit(hive, false)
endif
static if LIBRARY_SelectionCircle then
set circle = SelectionCircle.attach_to_unit(hive, DATA(16, level), RAW_ID(3))
set circle.data = this
call circle.set_listener_event(function thistype.hive_whileSelected)
call circle.set_show(false)
set circle2 = SelectionCircle.attach_to_unit(hive, DATA(1, level), RAW_ID(5))
set circle2.data = this
call circle2.set_listener_event(function thistype.hive_whileSelected)
call circle2.set_show(false)
endif
call active_push()
set active_head = 1
if thistype(0).active_head == 0 then
call TimerStart(ACTIVE_TIMER, DATA(10, 0), true, function thistype.hive_runList)
endif
set thistype(0).active_head = thistype(0).active_head + 1
static if ADV_DEBUG then
call echo("thistype(0).active_head: Number of active instances is " + I2S(thistype(0).active_head))
call line_Break()
endif
endmethod
private static method prepare_hive_flag takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call hive_create()
endmethod
private method prepare_hive takes nothing returns nothing
set timer = NewTimerEx(this)
set timer2 = NewTimerEx(this)
call TimerStart(timer, real_GetDist(GetUnitX(unit), GetUnitY(unit), GetSpellTargetX(), GetSpellTargetY())/DATA(9, 0), false, function thistype.prepare_hive_flag)
endmethod
static method create takes unit u returns thistype
local thistype this = get(u)
local thistype that
if this == 0 then
set this = allocate()
set unit = u
static if ADV_DEBUG then
call echo("Instance of unit has been created.")
call line_Break()
endif
set level = GetUnitAbilityLevel(u, SPELL_ID())
set head = 1
call UnitAddAbility(u, RAW_ID(2))
call UnitMakeAbilityPermanent(u, true, RAW_ID(2))
call push()
call local_insert(this)
set tx = GetSpellTargetX()
set ty = GetSpellTargetY()
call prepare_hive()
return this
endif
set that = allocate()
set that.unit = unit
static if ADV_DEBUG then
call echo("Allocated another instance.")
call line_Break()
endif
set that.level = GetUnitAbilityLevel(unit, SPELL_ID())
call that.local_insert(this)
set that.tx = GetSpellTargetX()
set that.ty = GetSpellTargetY()
call UnitAddAbility(u, RAW_ID(2))
call UnitMakeAbilityPermanent(u, true, RAW_ID(2))
call that.prepare_hive()
return that
endmethod
static method teleport takes unit u, real x, real y returns nothing
local thistype this = get(u)
local thistype head = this
local thistype nearest = head
if this == 0 then
call echo("thistype.teleport: Instance of unit does not exist.")
call echo("thistype.teleport: Returning")
return
endif
set this = local_next
loop
exitwhen this == head
if (real_GetDist(x, y, GetUnitX(hive), GetUnitY(hive)) <= real_GetDist(x, y, GetUnitX(nearest.hive), GetUnitY(nearest.hive))) and mode == 2 then
set nearest = this
endif
set this = local_next
endloop
if nearest.mode != 2 then
call DisplayTextToPlayer(GetOwningPlayer(u), 0, 0, "|cffffcc00The hives are not available.|r")
call IssueImmediateOrderById(u, 851972)
else
call nearest.overmind_delayTeleport()
//call nearest.overmind_onTeleport()
endif
endmethod
// Init method
public static method init takes nothing returns nothing
local trigger t = CreateTrigger()
set target_search_code = function thistype.onTarget_search
set hive_onFeedback_loop_code = function thistype.hive_onFeedback_loop
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, function thistype.hive_onDeathEvent)
call TriggerAddCondition(t, function thistype.target_onDeathEvent)
set t = CreateTrigger()
call Damage.registerModifierTrigger(t)
call TriggerAddCondition(t, function thistype.hive_spider_onDamage)
// Located at part two
set t = null
endmethod
endstruct
private function OnSpellEvent takes nothing returns nothing
if GetSpellAbilityId() == SPELL_ID() then
call Data.create(GetTriggerUnit())
elseif GetSpellAbilityId() == RAW_ID(2) then
call Data.teleport(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function OnSpellEvent)
call ForForce(bj_FORCE_PLAYER[0], function Data.init)
set t = null
endfunction
endlibrary
scope Command initializer Init
private function Clear takes nothing returns nothing
call ClearTextMessages()
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-clear", false)
call TriggerAddCondition(t, function Clear)
set t = null
endfunction
endscope
function Trig_Set_Unit_Animation_Actions takes nothing returns nothing
call SetUnitTimeScalePercent( gg_unit_E000_0059, 10.00 )
call TriggerExecute(gg_trg_Loop_Unit_Animation)
endfunction
//===========================================================================
function InitTrig_Set_Unit_Animation takes nothing returns nothing
set gg_trg_Set_Unit_Animation = CreateTrigger( )
call TriggerAddAction( gg_trg_Set_Unit_Animation, function Trig_Set_Unit_Animation_Actions )
endfunction
library ToxicIllusionConfig requires Missile, ListT, Alloc, Table, AnimationManager
native UnitAlive takes unit id returns boolean
module ToxicPoolConfig
static method targetFilter takes unit targ, unit source returns boolean
return UnitAlive(targ) and IsUnitEnemy(targ, GetOwningPlayer(source)) /*
*/ and not IsUnitType(targ, UNIT_TYPE_MECHANICAL) /*
*/ and not IsUnitType(targ, UNIT_TYPE_STRUCTURE) /*
*/ and GetUnitFlyHeight(targ) <= 50
endmethod
static method POOL_DURATION takes integer level returns real
return 15.
endmethod
endmodule
module ToxicIllusionConfig
readonly static constant integer SPELL_ID = 'A000'
readonly static constant integer BUFF_ID = 'B000'
readonly static constant integer ILLUSION_ID = '0000'
readonly static constant integer ILLUSION_ORDER = 852274
readonly static constant real INTERVAL = 1/32.
readonly static constant real VENOM_INTERVAL = 1.
readonly static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
readonly static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_POISON
static method FADE_DURATION takes integer lev returns real
return 1.5
endmethod
// Returns the default vertex color of the unit.
// 0 - Red, 1 - Green, 2 - Blue, 3 - Alpha
static method COLOR takes integer req returns integer
if req == 1 then
return 0
endif
return 0xff
endmethod
static method FINAL_COLOR takes integer req returns integer
if req == 1 then
return 0xff
endif
return 0
endmethod
static method MODEL_FILE takes integer req returns string
if req == 0 then
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif req == 1 then
return "Abilities\\Spells\\Human\\CloudOfFog\\CloudOfFog.mdl"
elseif req == 2 then
return "Abilities\\Weapons\\PoisonSting\\PoisonStingTarget.mdl"
endif
return ""
endmethod
// The speed of the venomous projectile
static method VENOM_SPEED takes nothing returns real
return 900.
endmethod
// The enumeration radius for dealing damage
static method VENOM_RADIUS takes integer level returns real
return 175. + 25*level
endmethod
// The amount of damage dealt
static method VENOM_DAMAGE takes integer level returns real
return 10. + 15*level
endmethod
// The duration of the illusion
static method VENOM_COPY_DURATION takes integer level returns real
return 10. + 5*level
endmethod
static method VENOM_COPY_DAMAGE_DEALT takes integer level returns real
// 3*Instruction + level
// Instruction: 0 (Damage dealt)
return 1.0
endmethod
static method VENOM_COPY_DAMAGE_TAKEN takes integer level returns real
return 2.5 - 0.5*level
endmethod
static method FOUNTAIN_DAMAGE takes integer level returns real
return (60 + 40*level)/FADE_DURATION(level)*INTERVAL
endmethod
endmodule
endlibrary
library ToxicIllusion requires ToxicIllusionConfig
/*
* Toxic Illusion
* - Hides the caster for a period of time, rendering the caster invulnerable.
* The caster can actually gain experience due to the caster having its
* position updated every tick.
*
* - Upon death of illusion, the caster spawns at the target location when the
* spell was cast.
*/
private struct ToxicVisibility extends array
implement Alloc
integer startAlpha
integer deltaAlpha
integer maxTicks
integer ticks
unit target
method destroy takes nothing returns nothing
set this.startAlpha = 0
set this.deltaAlpha = 0
set this.maxTicks = 0
set this.ticks = 0
set this.target = null
call this.deallocate()
endmethod
method update takes nothing returns nothing
local integer r = BlzGetUnitIntegerField(this.target, UNIT_IF_TINTING_COLOR_RED)
local integer g = BlzGetUnitIntegerField(this.target, UNIT_IF_TINTING_COLOR_GREEN)
local integer b = BlzGetUnitIntegerField(this.target, UNIT_IF_TINTING_COLOR_BLUE)
local integer a
set this.ticks = IMinBJ(this.ticks + 1, this.maxTicks)
set a = this.startAlpha + R2I(this.deltaAlpha*(this.ticks / I2R(this.maxTicks)))
call SetUnitVertexColor(this.target, r, g, b, a)
endmethod
static method create takes unit whichunit, integer start, integer end, integer maxTicks returns thistype
local thistype this = thistype.allocate()
set this.target = whichunit
set this.startAlpha = start
set this.deltaAlpha = end - start
set this.maxTicks = maxTicks
set this.ticks = 0
return this
endmethod
endstruct
private struct ToxicFountain extends array
private static Table visualMap = 0
private static group list = null
private static timer countdown = null
private static method generateFountains takes nothing returns nothing
local integer iter = 0
local integer vis = 0
local integer size = BlzGroupGetSize(thistype.list)
local unit base = BlzGroupUnitAt(thistype.list, iter)
local effect fx
loop
exitwhen iter >= size
set fx = AddSpecialEffect(ToxicIllusion.MODEL_FILE(0), /*
*/ GetUnitX(base), GetUnitY(base))
set vis = thistype.visualMap[GetHandleId(base)]
call DestroyEffect(fx)
call ToxicVisibility(vis).update()
set iter = iter + 1
set base = BlzGroupUnitAt(thistype.list, iter)
endloop
if BlzGroupGetSize(thistype.list) <= 0 then
call PauseTimer(thistype.countdown)
endif
set base = null
set fx = null
endmethod
static method addInstance takes unit instance, real interval, integer vis returns nothing
local integer id = GetHandleId(instance)
if thistype.visualMap.has(id) then
return
endif
call GroupAddUnit(thistype.list, instance)
set thistype.visualMap[id] = ToxicVisibility(vis)
if BlzGroupGetSize(thistype.list) == 1 then
call TimerStart(thistype.countdown, interval, true, /*
*/function thistype.generateFountains)
endif
endmethod
static method removeInstance takes unit instance returns nothing
local ToxicVisibility vis
local integer id = GetHandleId(instance)
if not thistype.visualMap.has(id) then
return
endif
set vis = ToxicVisibility(thistype.visualMap[id])
call vis.destroy()
call thistype.visualMap.remove(id)
call GroupRemoveUnit(thistype.list, instance)
endmethod
private static method onInit takes nothing returns nothing
set thistype.list = CreateGroup()
set thistype.countdown = CreateTimer()
set thistype.visualMap = Table.create()
endmethod
endstruct
struct ToxicPool extends array
implement Alloc
private static group ENUM_GROUP = CreateGroup()
static string TOXIC_POP = ""
static string TOXIC_TARGET = ""
attacktype atktype
damagetype dmgtype
real damage
real interval
real range
effect pool
readonly real tx
readonly real ty
readonly unit source
private timer activator
implement ToxicPoolConfig
method destroy takes nothing returns nothing
call ReleaseTimer(this.activator)
call DestroyEffect(this.pool)
set this.activator = null
set this.source = null
set this.pool = null
set this.dmgtype = null
set this.atktype = null
set this.ty = 0
set this.tx = 0
set this.range = 0
set this.interval = 0
set this.damage = 0
call this.deallocate()
endmethod
private static method onPoolDamage takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local unit enumUnit
call DestroyEffect( AddSpecialEffect(TOXIC_POP, this.tx, this.ty))
call GroupEnumUnitsInRange(ENUM_GROUP, this.tx, this.ty, this.range, null)
loop
set enumUnit = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, enumUnit)
exitwhen enumUnit == null
if thistype.targetFilter(enumUnit, this.source) then
call UnitDamageTarget(this.source, enumUnit, this.damage, /*
*/false, false, /*
*/this.atktype, this.dmgtype, null)
call DestroyEffectTimed(AddSpecialEffectTarget(TOXIC_TARGET, enumUnit, "chest"), 0.5)
endif
endloop
endmethod
method activate takes nothing returns nothing
if this.activator != null then
return
endif
set this.activator = NewTimerEx(this)
call TimerStart(this.activator, this.interval, true, function thistype.onPoolDamage)
endmethod
static method create takes unit source, real tx, real ty returns thistype
local thistype this = thistype.allocate()
set this.source = source
set this.tx = tx
set this.ty = ty
return this
endmethod
endstruct
globals
private code venomPoolCallback = null
private integer projectileData = 0
private real projectileX = 0
private real projectileY = 0
endglobals
private struct ToxicProjectile extends array
private static trigger eval = CreateTrigger()
static method onFinish takes Missile missile returns boolean
set projectileData = missile.data
set projectileX = missile.x
set projectileY = missile.y
call TriggerEvaluate(eval)
return true
endmethod
static method init takes nothing returns nothing
call TriggerAddCondition(eval, Condition(venomPoolCallback))
endmethod
implement MissileStruct
endstruct
struct ToxicIllusion extends array
implement Alloc
implement ToxicIllusionConfig
private static timer ownerTimer = CreateTimer()
private static unit dummyCaster = null
private static unit illusionUnit = null
private static Table illusionMap = 0
private static IntegerList ownerList = 0
private integer lock
private Table poolMap
private IntegerListItem ownerPoint
readonly unit owner
readonly unit illusion
readonly integer level
readonly IntegerList poolList
readonly real tx
readonly real ty
private method destroy takes nothing returns nothing
call this.poolMap.destroy()
call this.poolList.destroy()
set this.tx = 0
set this.ty = 0
set this.poolList = 0
set this.poolMap = 0
set this.level = 0
set this.lock = 0
set this.illusion = null
set this.owner = null
call this.deallocate()
endmethod
private method checkForDestroy takes nothing returns nothing
if UnitAlive(this.illusion) then
return
elseif this.poolList.size() > 0 then
return
elseif this.lock > 0 then
return
endif
call this.destroy()
endmethod
private method suspendOwner takes boolean flag returns nothing
call ShowUnit(this.owner, not flag)
call BlzPauseUnitEx(this.owner, flag)
call SetUnitInvulnerable(this.owner, flag)
endmethod
private method activateIllusion takes boolean flag returns nothing
call PauseUnit(this.illusion, not flag)
call ShowUnit(this.illusion, flag)
endmethod
private method createVenom takes nothing returns nothing
local Missile venom = Missile.createXYZ(GetUnitX(this.owner), GetUnitY(this.owner), 20, /*
*/ this.tx, this.ty, 0)
call venom.setMovementSpeed(thistype.VENOM_SPEED())
set venom.arc = 0.4
set venom.model = MODEL_FILE(0)
set venom.data = this
call ToxicProjectile.launch(venom)
endmethod
private static method updateOwnerPosition takes nothing returns nothing
local IntegerListItem iter = ownerList.first
local thistype this = thistype(iter.data)
loop
exitwhen iter == 0
call SetUnitX(this.owner, GetUnitX(this.illusion))
call SetUnitY(this.owner, GetUnitY(this.illusion))
call SetUnitFacing(this.owner, GetUnitFacing(this.illusion))
set iter = iter.next
set this = thistype(iter.data)
endloop
if ownerList.size() == 0 then
call PauseTimer(ownerTimer)
endif
endmethod
private method hookOwner takes boolean flag returns nothing
if flag and (this.ownerPoint == IntegerListItem(0)) then
set this.ownerPoint = ownerList.push(this).last
if ownerList.size() == 1 then
call TimerStart(ownerTimer, INTERVAL, true, function thistype.updateOwnerPosition)
endif
elseif (not flag) and (this.ownerPoint != IntegerListItem(0)) then
call ownerList.erase(this.ownerPoint)
set this.ownerPoint = 0
endif
endmethod
private static method revertRemoveFountain takes nothing returns nothing
local thistype this = thistype(ReleaseTimer(GetExpiredTimer()))
call UnitRemoveAnimationLoop(this.owner)
call ToxicFountain.removeInstance(this.owner)
set this.lock = this.lock - 1
call this.checkForDestroy()
endmethod
private method revertCreateFountain takes nothing returns nothing
local timer temp = NewTimerEx(this)
local ToxicVisibility vis = ToxicVisibility.create(this.owner, /*
*/COLOR(1), FINAL_COLOR(1), /*
*/R2I(FADE_DURATION(this.level) / INTERVAL))
set this.lock = this.lock + 1
call ToxicFountain.addInstance(this.owner, INTERVAL, vis)
call UnitAddAnimationLoop(this.owner, "spell", 1.00)
call TimerStart(temp, thistype.FADE_DURATION(this.level), /*
*/false, function thistype.revertRemoveFountain)
endmethod
private static method onIllusionSummon takes nothing returns nothing
if GetSummoningUnit() != dummyCaster then
return
endif
set illusionUnit = GetSummonedUnit()
endmethod
private static method createIllusion takes unit owner returns unit
call SetUnitX(dummyCaster, GetUnitX(owner))
call SetUnitY(dummyCaster, GetUnitY(owner))
call IssueTargetOrderById(dummyCaster, ILLUSION_ORDER, owner)
// The illusion has been summoned
call SetUnitOwner(illusionUnit, GetOwningPlayer(owner), true)
return illusionUnit
endmethod
private static method onIllusionDeath takes nothing returns nothing
local unit illu = GetTriggerUnit()
local integer id = GetHandleId(illu)
local thistype this
if not thistype.illusionMap.has(id) then
set illu = null
return
endif
set this = thistype.illusionMap[id]
call SetUnitX(this.owner, GetUnitX(illu))
call SetUnitY(this.owner, GetUnitY(illu))
call SetUnitFacing(this.owner, GetUnitFacing(illu))
call this.revertCreateFountain()
call this.hookOwner(false)
call this.suspendOwner(false)
call this.checkForDestroy()
if IsUnitSelected(illu, GetOwningPlayer(illu)) and /*
*/ (GetLocalPlayer() == GetOwningPlayer(illu)) then
call SelectUnit(this.owner, true)
endif
set illu = null
endmethod
private static method onIllusionDamage takes nothing returns nothing
local unit src = GetEventDamageSource()
local unit targ = GetTriggerUnit()
local integer srcID = GetHandleId(src)
local integer targID = GetHandleId(targ)
local thistype this
local thistype that
if thistype.illusionMap.has(srcID) then
set this = thistype.illusionMap[srcID]
call BlzSetEventDamage(GetEventDamage()*thistype.VENOM_COPY_DAMAGE_DEALT(this.level))
endif
if thistype.illusionMap.has(targID) then
set that = thistype.illusionMap[targID]
call BlzSetEventDamage(GetEventDamage()*thistype.VENOM_COPY_DAMAGE_TAKEN(that.level))
endif
set src = null
set targ = null
endmethod
private static method delayedRemovePool takes nothing returns nothing
local thistype this = ReleaseTimer(GetExpiredTimer())
local IntegerListItem iter = this.poolList.first
local ToxicPool pool = iter.data
loop
exitwhen iter == 0
call this.poolMap.remove(pool)
call this.poolList.erase(iter)
call pool.destroy()
set iter = this.poolList.first
set pool = iter.data
endloop
call this.checkForDestroy()
endmethod
private static method onMissileLand takes nothing returns nothing
local thistype this = thistype(projectileData)
local ToxicPool pool
call DestroyEffect(AddSpecialEffect(MODEL_FILE(0), projectileX, projectileY))
set this.tx = projectileX
set this.ty = projectileY
set pool = ToxicPool.create(this.owner, this.tx, this.ty)
set pool.interval = VENOM_INTERVAL
set pool.damage = VENOM_DAMAGE(this.level)
set pool.range = VENOM_RADIUS(this.level)
set pool.atktype = ATTACK_TYPE
set pool.dmgtype = DAMAGE_TYPE
set pool.pool = AddSpecialEffect(MODEL_FILE(1), pool.tx, pool.ty)
call pool.activate()
call this.poolList.push(pool)
set this.poolMap[pool] = this.poolList.last
call TimerStart(NewTimerEx(this), ToxicPool.POOL_DURATION(this.level), /*
*/false, function thistype.delayedRemovePool)
endmethod
private static method removeFountain takes nothing returns nothing
local thistype this = thistype(ReleaseTimer(GetExpiredTimer()))
local boolean flag = IsUnitSelected(this.owner, GetOwningPlayer(this.owner))
call UnitRemoveAnimationLoop(this.owner)
call BlzPauseUnitEx(this.owner, false)
call ToxicFountain.removeInstance(this.owner)
call this.activateIllusion(true)
call this.createVenom()
call this.suspendOwner(true)
call this.hookOwner(true)
if flag and (GetLocalPlayer() == GetOwningPlayer(this.owner)) then
call SelectUnit(this.illusion, true)
endif
call SetUnitX(this.illusion, GetUnitX(this.owner))
call SetUnitY(this.illusion, GetUnitY(this.owner))
call UnitApplyTimedLife(this.illusion, 'BTLF', VENOM_COPY_DURATION(this.level))
endmethod
private static method create takes unit owner, unit target returns thistype
local thistype this = thistype.allocate()
if target == null then
set this.tx = GetSpellTargetX()
set this.ty = GetSpellTargetY()
else
set this.tx = GetUnitX(target)
set this.ty = GetUnitY(target)
endif
set this.owner = owner
set this.level = GetUnitAbilityLevel(owner, SPELL_ID)
set this.poolList = IntegerList.create()
set this.illusion = thistype.createIllusion(owner)
set this.poolMap = Table.create()
set illusionMap[GetHandleId(this.illusion)] = this
return this
endmethod
private method createFountain takes nothing returns nothing
local timer temp = NewTimerEx(this)
local ToxicVisibility vis = ToxicVisibility.create(this.owner, /*
*/COLOR(0), FINAL_COLOR(0), /*
*/R2I(FADE_DURATION(this.level) / INTERVAL))
call ToxicFountain.addInstance(this.owner, INTERVAL, vis)
call UnitAddAnimationLoop(this.owner, "spell", 1.00)
call TimerStart(temp, thistype.FADE_DURATION(this.level), /*
*/false, function thistype.removeFountain)
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this
if GetSpellAbilityId() != thistype.SPELL_ID then
return
endif
set this = thistype.create(GetTriggerUnit(), GetSpellTargetUnit())
call this.activateIllusion(false)
call this.createFountain()
call BlzPauseUnitEx(this.owner, true)
endmethod
private static method initCaster takes nothing returns nothing
set thistype.dummyCaster = GetRecycledDummyAnyAngle(0, 0, 0)
call UnitAddAbility(thistype.dummyCaster, ILLUSION_ID)
call BlzUnitDisableAbility(thistype.dummyCaster, 'Amov', true, false)
endmethod
private static method initTriggers takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function thistype.onSpellEffect))
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SUMMON)
call TriggerAddCondition(trig, Condition(function thistype.onIllusionSummon))
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(trig, Condition(function thistype.onIllusionDeath))
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DAMAGED)
call TriggerAddCondition(trig, Condition(function thistype.onIllusionDamage))
endmethod
private static method onInit takes nothing returns nothing
call thistype.initCaster()
call thistype.initTriggers()
set venomPoolCallback = function thistype.onMissileLand
set illusionMap = Table.create()
set ownerList = IntegerList.create()
set ToxicPool.TOXIC_POP = MODEL_FILE(0)
set ToxicPool.TOXIC_TARGET = MODEL_FILE(2)
call ToxicProjectile.init()
endmethod
endstruct
endlibrary
library FelMagicSynthesisConfig requires Missile, ListT, Alloc, Table
module FelMagicSynthesisConfig
readonly static constant integer SPELL_ID = 'A001'
// The sub-ability added on learning
readonly static constant integer SUB_SPELL = '1000'
// Read methods
// This returns the percentage of damage that becomes additional mana.
static method MANA_BONUS takes integer lev returns real
if lev == 1 then
return 0.07
elseif lev == 2 then
return 0.1
elseif lev == 3 then
return 0.13
endif
return 0.
endmethod
// Returns the minimum amount of damage to be considered as a damage instance
static method MIN_DAMAGE_ACCEPT takes integer lev returns real
return 35. - 5*lev
endmethod
// Returns the amount of damage instances required to generate one spider
static method DMG_INSTANCE_SPIDER takes integer lev returns integer
return 8 - lev
endmethod
// Returns the maximum number of spiders for each level
static method SPIDER_MAXIMUM_COUNT takes integer lev returns integer
return lev + 2
endmethod
// How long will the spider remain alive?
static method SPIDER_DURATION takes integer lev returns real
return 5. + 5*lev
endmethod
// Returns the raw-code of the spiders to be spawned.
static method SPIDER_ID takes integer lev returns integer
if lev == 1 then
return 'e003'
elseif lev == 2 then
return 'e004'
elseif lev == 3 then
return 'e005'
endif
return 0
endmethod
// The spawn distance of the spiders
static method SPIDER_SPAWN_DIST takes integer level returns real
if level == 1 then
return GetRandomReal(300., 400.)
elseif level == 2 then
return GetRandomReal(350., 500.)
elseif level == 3 then
return GetRandomReal(400., 600.)
endif
return 0.
endmethod
// The amount of time taken for the spider/s to spawn
static method SPIDER_SPAWN_DELAY takes nothing returns real
return 1.
endmethod
// Missile speed
static method MISSILE_SPEED takes nothing returns real
return 400.
endmethod
// This stores paths for model files
static method MODEL_FILE takes integer req returns string
if req == 0 then
// The egg missile, generated when casting the sub-spell.
return "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
elseif req == 1 then
// The splash missile, played upon hitting.
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif req == 2 then
return "Doodads\\Dungeon\\Terrain\\EggSack\\EggSack0.mdl"
endif
return ""
endmethod
endmodule
native UnitAlive takes unit id returns boolean
module ConvertLifeForceConfig
readonly static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
readonly static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
static method targetFilter takes unit target, unit source returns boolean
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(source)) /*
*/ and not IsUnitType(target, UNIT_TYPE_STRUCTURE) /*
*/ and not IsUnitType(target, UNIT_TYPE_MECHANICAL) /*
*/ and (GetUnitFlyHeight(target) - GetUnitFlyHeight(source)) <= 120
endmethod
static method MANA_COMBUST_RATIO takes integer level returns real
return -0.25 + 0.75*level
endmethod
static method MANA_COMBUST_RADIUS takes integer level returns real
return 300.
endmethod
static method MODEL_FILE takes integer req returns string
if req == 0 then
// The egg missile, generated when casting the sub-spell.
return "Objects\\Spawnmodels\\NightElf\\NECancelDeath\\NECancelDeath.mdl"
endif
return ""
endmethod
endmodule
endlibrary
library FelMagicSynthesis initializer Init requires FelMagicSynthesisConfig
/*
* Fel Magic Synthesis
* - Each damage dealt by the Overmind is converted to additional mana.
*
* - Includes a sub-spell that updates its' information every time the
* ability is skilled.
*
* - Every damage instance will count if and only if the damage exceeds
* the minimum amount. This applies to spider generation.
*
*/
globals
private unit projectileSource = null
private code projectileCallback = null
private real projectileX = 0
private real projectileY = 0
endglobals
private struct FelMagicProjectile extends array
private static trigger eval = CreateTrigger()
static method onFinish takes Missile missile returns boolean
set projectileSource = missile.source
set projectileX = missile.x
set projectileY = missile.y
call TriggerEvaluate(eval)
return true
endmethod
static method onTerrain takes Missile missile returns boolean
return onFinish(missile)
endmethod
static method init takes nothing returns nothing
call TriggerAddCondition(eval, Condition(projectileCallback))
endmethod
implement MissileStruct
endstruct
private struct FelMagicHatchery extends array
implement Alloc
integer unitType
real tx
real ty
real delay
real lifetime
effect eggsack
readonly unit source
private timer expire
private method destroy takes nothing returns nothing
call ReleaseTimer(this.expire)
call DestroyEffect(this.eggsack)
set this.expire = null
set this.source = null
set this.eggsack = null
set this.lifetime = 0.
set this.delay = 0.
set this.ty = 0.
set this.tx = 0.
set this.unitType = 0
call this.deallocate()
endmethod
private static method onSpawnSpider takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local unit spider = CreateUnit(GetOwningPlayer(this.source), this.unitType, /*
*/ this.tx, this.ty, bj_UNIT_FACING)
call UnitApplyTimedLife(spider, 'BTLF', this.lifetime)
call this.destroy()
set spider = null
endmethod
method activate takes nothing returns nothing
if this.expire != null then
return
endif
set this.expire = NewTimerEx(this)
call TimerStart(this.expire, this.delay, false, function thistype.onSpawnSpider)
endmethod
static method create takes unit source returns thistype
local thistype this = allocate()
set this.source = source
return this
endmethod
endstruct
private struct ConvertLifeForce extends array
private static group ENUM_GROUP = CreateGroup()
real dmgStored
unit unit
implement ConvertLifeForceConfig
method combust takes integer level returns nothing
local real cx = GetUnitX(this.unit)
local real cy = GetUnitY(this.unit)
local real dmg = this.dmgStored*MANA_COMBUST_RATIO(level)
local unit enumUnit
call DestroyEffect( AddSpecialEffect(MODEL_FILE(0), cx, cy))
call GroupEnumUnitsInRange(ENUM_GROUP, cx, cy, MANA_COMBUST_RADIUS(level), null)
loop
set enumUnit = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, enumUnit)
exitwhen enumUnit == null
if targetFilter(enumUnit, this.unit) then
call UnitDamageTarget(this.unit, enumUnit, dmg, true, false, /*
*/ ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
endmethod
endstruct
struct FelMagicSynthesis extends array
// Basic allocator module.
implement Alloc
implement FelMagicSynthesisConfig
// Member variables and configurables
// The learned ability, and a flag for registry.
private static Table instanceMap = 0
// The learning unit
private unit unit
// The level of the ability SPELL_ID
private integer level
// The amount of spiders to be spawned.
private integer spiderStack
// Exclusive to the struct!
// Getter of instance.
private static method getUnitIndex takes unit u returns thistype
return instanceMap[GetHandleId(u)]
endmethod
private static method setUnitIndex takes unit u, thistype value returns nothing
set instanceMap[GetHandleId(u)] = value
endmethod
private method operator lifeforce takes nothing returns ConvertLifeForce
return this
endmethod
// Missile methods
// Called when missile hits the target area.
private static method onMissileHit takes nothing returns nothing
local thistype this = getUnitIndex(projectileSource)
local FelMagicHatchery hatch = FelMagicHatchery.create(this.unit)
set hatch.tx = projectileX
set hatch.ty = projectileY
set hatch.eggsack = AddSpecialEffect(MODEL_FILE(2), hatch.tx, hatch.ty)
set hatch.delay = SPIDER_SPAWN_DELAY()
set hatch.lifetime = SPIDER_DURATION(this.level)
set hatch.unitType = SPIDER_ID(this.level)
call hatch.activate()
call DestroyEffect(AddSpecialEffect(MODEL_FILE(1), hatch.tx, hatch.ty))
endmethod
// unit methods
// Called when casting the sub-spell.
static method spawnSpiders takes unit whichunit returns nothing
local thistype this = getUnitIndex(whichunit)
local Missile egg = 0
local real cx = 0.
local real cy = 0.
local real theta = 0.
local real dist = 0.
local real tx = 0.
local real ty = 0.
call this.lifeforce.combust(this.level)
set this.lifeforce.dmgStored = 0
if this.spiderStack < DMG_INSTANCE_SPIDER(this.level) then
call BlzEndUnitAbilityCooldown(this.unit, SUB_SPELL)
return
endif
set cx = GetUnitX(this.unit)
set cy = GetUnitY(this.unit)
loop
exitwhen this.spiderStack < DMG_INSTANCE_SPIDER(this.level)
set theta = GetRandomReal(0., 1.)*2*bj_PI
set dist = SPIDER_SPAWN_DIST(this.level)
set tx = cx + dist*Cos(theta)
set ty = cy + dist*Sin(theta)
set egg = Missile.createXYZ(cx, cy, 30, tx, ty, 0)
set egg.model = MODEL_FILE(0)
set egg.data = this.level
set egg.source = this.unit
set egg.arc = 0.40
call egg.setMovementSpeed(MISSILE_SPEED())
call FelMagicProjectile.launch(egg)
set this.spiderStack = this.spiderStack - DMG_INSTANCE_SPIDER(this.level)
endloop
set this.spiderStack = 0
endmethod
static method create takes unit whichunit returns thistype
local thistype this = getUnitIndex(whichunit)
if IsUnitIllusion(whichunit) then
return 0
endif
if this == 0 then
set this = allocate()
set this.unit = whichunit
set this.lifeforce.unit = whichunit
call UnitAddAbility(this.unit, SUB_SPELL)
call UnitMakeAbilityPermanent(this.unit, true, SUB_SPELL)
call setUnitIndex(whichunit, this)
endif
set this.level = GetUnitAbilityLevel(this.unit, SPELL_ID)
call SetUnitAbilityLevel(this.unit, SUB_SPELL, this.level)
return this
endmethod
private static method onDamage takes nothing returns nothing
local thistype this = getUnitIndex(GetEventDamageSource())
local thistype that = getUnitIndex(GetTriggerUnit())
local integer stack = 0
local real dmg = GetEventDamage()
// Make sure that the damaging unit is not an illusion
if (this != thistype(0)) and (BlzGetEventDamageType() == DAMAGE_TYPE_NORMAL) then
call SetUnitState(this.unit, UNIT_STATE_MANA, /*
*/ GetUnitState(this.unit, UNIT_STATE_MANA) /*
*/+ dmg*MANA_BONUS(this.level))
endif
if (that != thistype(0)) and (BlzGetEventDamageType() == DAMAGE_TYPE_NORMAL) then
set stack = DMG_INSTANCE_SPIDER(that.level)*SPIDER_MAXIMUM_COUNT(that.level)
if dmg < MIN_DAMAGE_ACCEPT(that.level) then
return
elseif that.spiderStack >= stack then
return
endif
set that.spiderStack = that.spiderStack + 1
set that.lifeforce.dmgStored = that.lifeforce.dmgStored + dmg
endif
endmethod
private static method initTriggers takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DAMAGED)
call TriggerAddCondition(t, function thistype.onDamage)
endmethod
private static method initVariables takes nothing returns nothing
set instanceMap = Table.create()
set projectileCallback = function thistype.onMissileHit
endmethod
private static method onInit takes nothing returns nothing
call thistype.initTriggers()
call thistype.initVariables()
call FelMagicProjectile.init()
endmethod
endstruct
private function OnSpellCast takes nothing returns nothing
if GetSpellAbilityId() != FelMagicSynthesis.SUB_SPELL then
return
endif
call FelMagicSynthesis.spawnSpiders(GetTriggerUnit())
endfunction
private function OnSkillLearn takes nothing returns nothing
if GetLearnedSkill() != FelMagicSynthesis.SPELL_ID then
return
endif
call FelMagicSynthesis.create(GetTriggerUnit())
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_HERO_SKILL)
call TriggerAddCondition(trig, function OnSkillLearn)
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, function OnSpellCast)
endfunction
endlibrary
library SaccularGenesisConfig requires TimerUtils, Table, Alloc, ListT, Missile, BezierEasing /*
------------------------------
*/ optional TextTag, /*
------------------------------
------------------------------
*/ optional SelectionCircle /*
------------------------------
______________________________
*
* __________________________
*
* OvermindSpellThree
* __________________________
*
* ______________
*
* The Hive
* ______________
*
* - A spell based on Pocket Factory that plants a targeted location with a hive mind.
* - This spell is active, and has a sub-spell component, namely Ravage Hive.
*
* __________________
*
* How it goes
* __________________
*
* - The spell is cast, triggering a spell-effect trigger and creating an associated
* struct instance of type Data.
*
* - The Data struct instance then activates a timer, which is set to expire based on
* the speed of the missile created by the ability and the distance therefrom.
*
* - After expiration, the hive is created with a delay in its' timed life. During this
* time, the hive is rendered invulnerable.
*
* - After the delay, the expiration timer for the hive is started. The hive may or may
* not be harmed based on the data flag section.
*
* - If it dies, its' expiration timer expires, or a Ravage Hive sub-spell has taken effect,
* the hive will execute its' death-related event. This may bug out with certain scripts,
* which remove units on death.
*
______________________________
*/
native UnitAlive takes unit id returns boolean
module SaccularGenesisConfig
static constant integer SPELL_ID = 'A002'
static constant integer RAVAGE_ID = '1001'
static constant integer HATCHERY_ID = 'e001'
static constant integer SPIDERLING_ID = 'e002'
static constant integer ANTI_ATTACK_ID = '1002'
static constant integer ACTIVE_SELECTION_COLOR = 0xffffffff
static constant integer DEATH_SELECTION_COLOR = 0xff0000ff
static constant integer ACQUISITION_SELECTION_COLOR = 0x00ff00ff
static constant real MISSILE_SPEED = 1000.0
static constant real TIMER_INTERVAL = 1/32.
static constant real BEZIER_DURATION = 2.0
static constant real FADE_DURATION = 2.5
static constant real DEATH_DURATION = 5.0
private static BezierEasing bezier = 0
static method operator BEZIER takes nothing returns BezierEasing
return bezier
endmethod
static method BEZIER_EASE_CONFIG takes nothing returns BezierEasing
return BezierEasing.create(0.5, 0, 0.5, 1)
endmethod
static method filterTarget takes unit target, unit source returns boolean
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(source)) /*
*/ and not IsUnitType(target, UNIT_TYPE_STRUCTURE) /*
*/ and not IsUnitType(target, UNIT_TYPE_MECHANICAL) /*
*/ and not IsUnitType(target, UNIT_TYPE_FLYING)
endmethod
static method HIVE_INVULNERABLE takes integer level returns boolean
return true
endmethod
static method HIVE_DURATION takes integer level returns real
return 25. + 25*level
endmethod
static method HIVE_DETECT_RANGE takes integer level returns real
return 50. + 100*level
endmethod
static method HIVE_DEATH_DAMAGE takes integer level returns real
if level == 1 then
return 85.
elseif level == 2 then
return 150.
elseif level == 3 then
return 220.
endif
return HIVE_DEATH_DAMAGE + 75.*(level - 3)
endmethod
static method HIVE_GROWTH_DURATION takes integer level returns real
return 0.75
endmethod
static method SPIDER_SPAWN_COUNT takes integer level returns integer
return 3 + level
endmethod
static method SPIDER_WANDER_INTERVAL takes integer level returns real
return 1.
endmethod
static method SPIDER_STR_ATTACK takes integer level returns real
return 0.
endmethod
static method SPIDER_AGI_ATTACK takes integer level returns real
return 0.
endmethod
static method SPIDER_INT_ATTACK takes integer level returns real
return 0.06 + 0.04*level
endmethod
static method SPIDER_ROAM_RANGE takes integer level returns real
return HIVE_DETECT_RANGE(level)
endmethod
static method SPIDER_MAX_RANGE takes integer level returns real
return HIVE_DETECT_RANGE(level) + 25.*(5. + level)
endmethod
static method MANA_BURN_FLAT takes integer level returns real
if level == 1 then
return 5.
elseif level == 2 then
return 7.
elseif level == 3 then
return 10.
endif
return MANA_BURN_FLAT(3) + 5.*level
endmethod
static method MANA_BURN_RANGE takes integer level returns real
return 500.
endmethod
static method MANA_BURN_INTERVAL takes integer level returns real
return 1.
endmethod
static method MANA_REJUVENATE_RATIO takes integer level returns real
if level == 1 then
return 0.1
elseif level == 2 then
return 0.12
elseif level == 3 then
return 0.16
endif
return MANA_BURN_FLAT(3) + 0.04*(level - 3)
endmethod
static method NYDUS_DELAY takes integer level returns real
return 2.5
endmethod
static method MODEL_PATH takes integer req returns string
if req == 0 then
// Chimaera Acid
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif req == 1 then
// Mana burn effect
return "Abilities\\Spells\\NightElf\\ManaBurn\\ManaBurnTarget.mdl"
elseif req == 2 then
// Snap missile (Virtually useless)
return "Abilities\\Weapons\\snapMissile\\snapMissile.mdl"
elseif req == 3 then
// Detonate effect
return "Units\\NightElf\\Wisp\\WispExplode.mdl"
elseif req == 4 then
// Talk to me sign, denotes target of the hive.
return "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"
elseif req == 5 then
// Highlights the hives that are generated by the Fel spider
return "Abilities\\Spells\\Other\\Parasite\\ParasiteTarget.mdl"
elseif req == 6 then
// Special effect to be played when teleported.
return "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
elseif req == 7 then
// Special effect to be played when teleporting.
return "Abilities\\Spells\\Undead\\DeathPact\\DeathPactTarget.mdl"
elseif req == 8 then
// Special effect to be played as some sort of eruption effect.
return MODEL_PATH(6)
endif
return ""
endmethod
private static method onInit takes nothing returns nothing
set bezier = BEZIER_EASE_CONFIG()
endmethod
endmodule
module SaccularDetectorConfig
static constant real DETECTOR_INTERVAL = 1/10.
static method filterTarget takes unit target, unit source returns boolean
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(source)) /*
*/ and not IsUnitType(target, UNIT_TYPE_STRUCTURE) /*
*/ and not IsUnitType(target, UNIT_TYPE_MECHANICAL) /*
*/ and not IsUnitType(target, UNIT_TYPE_FLYING) /*
*/ and not BlzIsUnitInvulnerable(target)
endmethod
endmodule
module SaccularHatchBurnConfig
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_ENHANCED
static method filterTarget takes unit target, unit source returns boolean
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(source)) /*
*/ and not IsUnitType(target, UNIT_TYPE_STRUCTURE) /*
*/ and not IsUnitType(target, UNIT_TYPE_MECHANICAL) /*
*/ and not IsUnitType(target, UNIT_TYPE_FLYING) /*
*/ and not BlzIsUnitInvulnerable(target)
endmethod
endmodule
endlibrary
library SaccularGenesis initializer Init requires SaccularGenesisConfig
/*
TO-DO:
- Introduce OOP functionality to private classes.
- Do not just leave them hanging.
*/
globals
private group ENUM_GROUP = CreateGroup()
endglobals
private struct ExecuteInterface
private trigger trig
method destroy takes nothing returns nothing
call DestroyTrigger(this.trig)
call this.deallocate()
endmethod
method run takes nothing returns nothing
call TriggerEvaluate(this.trig)
endmethod
method setCode takes code func returns nothing
call TriggerClearConditions(this.trig)
call TriggerAddCondition(this.trig, Condition(func))
endmethod
static method create takes code func returns thistype
local thistype this = allocate()
set this.trig = CreateTrigger()
call TriggerAddCondition(this.trig, Condition(func))
return this
endmethod
endstruct
private struct SaccularSpider extends array
implement Alloc
private static Table spiderMap = 0
readonly unit owner
readonly unit spider
real strRatio
real agiRatio
real intRatio
IntegerListItem pointer
method destroy takes nothing returns nothing
set this.strRatio = 0.
set this.agiRatio = 0.
set this.intRatio = 0.
set this.pointer = 0
set this.owner = null
set this.spider = null
call this.deallocate()
endmethod
method clear takes boolean kill returns nothing
if kill then
call KillUnit(this.spider)
endif
call spiderMap.remove(GetHandleId(this.spider))
call this.deallocate()
endmethod
static method create takes unit owner, integer unitId, real cx, real cy returns thistype
local thistype this = allocate()
set this.owner = owner
set this.spider = CreateUnit(GetOwningPlayer(owner), unitId, cx, cy, bj_UNIT_FACING)
set spiderMap[GetHandleId(this.spider)] = this
return this
endmethod
private static method onSpiderDamage takes nothing returns nothing
local unit source = GetEventDamageSource()
local integer id = GetHandleId(source)
local thistype this
local real addDmg = 0.
if not spiderMap.has(id) then
set source = null
return
elseif BlzGetEventDamageType() != DAMAGE_TYPE_NORMAL then
set source = null
return
endif
set this = spiderMap[id]
set addDmg = addDmg + GetHeroStr(this.owner, true)*this.strRatio
set addDmg = addDmg + GetHeroAgi(this.owner, true)*this.agiRatio
set addDmg = addDmg + GetHeroInt(this.owner, true)*this.intRatio
call BlzSetEventDamage(GetEventDamage() + addDmg)
set source = null
endmethod
private static method initVariables takes nothing returns nothing
set spiderMap = Table.create()
endmethod
private static method initTriggers takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DAMAGING)
call TriggerAddCondition(trig, Condition(function thistype.onSpiderDamage))
endmethod
private static method onInit takes nothing returns nothing
call initVariables()
call initTriggers()
endmethod
endstruct
private struct SaccularDetector extends array
implement SaccularDetectorConfig
private static constant timer ENUM_TIMER = CreateTimer()
private static rect ENUM_RECT = null
private static IntegerList enumList = 0
private static unit enumUnit = null
private effect notary
private IntegerListItem pointer
readonly unit target
readonly group auraGroup
string notaryPath
real chaseRange
real range
unit source
IntegerList spiderList
private method addNotary takes nothing returns nothing
if this.notary != null then
return
endif
set this.notary = AddSpecialEffectTarget(this.notaryPath, this.target, "overhead")
endmethod
private method removeNotary takes nothing returns nothing
if this.notary == null then
return
endif
call DestroyEffect(this.notary)
set this.notary = null
endmethod
private method checkTarget takes unit target returns boolean
return filterTarget(target, this.source) and /*
*/ IsUnitInRange(target, this.source, this.chaseRange)
endmethod
private method checkTargetGroup takes nothing returns nothing
local integer i = 0
local integer n = BlzGroupGetSize(this.auraGroup)
local boolean skip = false
if this.checkTarget(this.target) then
return
endif
set this.target = null
call this.removeNotary()
loop
exitwhen i >= n
set enumUnit = BlzGroupUnitAt(this.auraGroup, i)
set skip = false
if not IsUnitInRange(enumUnit, this.source, this.range) and /*
*/ (GetUnitTypeId(enumUnit) != 0) then
call GroupRemoveUnit(this.auraGroup, enumUnit)
set i = i - 1
set n = n - 1
set skip = true
endif
if not skip then
if filterTarget(enumUnit, this.source) then
set this.target = enumUnit
call this.addNotary()
exitwhen true
endif
endif
set i = i + 1
endloop
endmethod
private method addNewTargets takes nothing returns nothing
local integer i = 0
local integer n
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(this.source), GetUnitY(this.source), /*
*/ this.range, null)
set n = BlzGroupGetSize(ENUM_GROUP)
loop
exitwhen i >= n
set enumUnit = BlzGroupUnitAt(ENUM_GROUP, i)
if not IsUnitInGroup(enumUnit, this.auraGroup) and filterTarget(enumUnit, this.source) then
call GroupAddUnit(this.auraGroup, enumUnit)
if this.target == null then
set this.target = enumUnit
call this.addNotary()
exitwhen true
endif
endif
set i = i + 1
endloop
endmethod
private method aggroTarget takes nothing returns nothing
local IntegerListItem iter = this.spiderList.first
local SaccularSpider spider
if this.target == null then
return
endif
loop
exitwhen iter == 0
set spider = iter.data
set iter = iter.next
call IssueTargetOrder(spider.spider, "attack", this.target)
endloop
endmethod
private static method detectTargets takes nothing returns nothing
local IntegerListItem iter = enumList.first
local thistype this
loop
exitwhen iter == 0
set this = iter.data
set iter = iter.next
call this.checkTargetGroup()
call this.addNewTargets()
call this.aggroTarget()
endloop
if enumList.empty() then
call PauseTimer(ENUM_TIMER)
endif
endmethod
method flush takes nothing returns nothing
if this.pointer == 0 then
return
endif
call enumList.erase(this.pointer)
call this.removeNotary()
call DestroyGroup(this.auraGroup)
set this.spiderList = 0
set this.range = 0
set this.pointer = 0
set this.notaryPath = ""
set this.source = null
set this.auraGroup = null
endmethod
method initialize takes real range returns thistype
set this.range = range
if this.auraGroup != null then
return this
endif
set this.auraGroup = CreateGroup()
set this.pointer = enumList.push(this).last
if enumList.size() == 1 then
call TimerStart(ENUM_TIMER, DETECTOR_INTERVAL, true, function thistype.detectTargets)
endif
return this
endmethod
private static method onInit takes nothing returns nothing
set enumList = IntegerList.create()
set ENUM_RECT = Rect(0., 0., 0., 0.)
endmethod
endstruct
private struct SaccularHatchRoam extends array
timer countdown
real range
method clear takes nothing returns nothing
if this.countdown == null then
return
endif
call ReleaseTimer(this.countdown)
set this.countdown = null
set this.range = 0.
endmethod
method wander takes IntegerList spiderList, unit origin returns nothing
local IntegerListItem iter = spiderList.first
local SaccularSpider spider
local real cx = GetUnitX(origin)
local real cy = GetUnitY(origin)
local real dist
local real theta
loop
exitwhen iter == 0
set spider = iter.data
set iter = iter.next
set dist = GetRandomReal(0., 1.)*this.range
set theta = GetRandomReal(0., 1.)*2*bj_PI
call IssuePointOrder(spider.spider, "move", /*
*/ cx + dist*Cos(theta), /*
*/ cy + dist*Sin(theta))
endloop
endmethod
endstruct
private struct SaccularHatchBurn extends array
implement SaccularHatchBurnConfig
private static unit enumUnit
string combustPath
real manaBurned
real manaBurnAmount
real manaRestore
real range
real manaDetonate
timer countdown
method clear takes nothing returns nothing
if this.countdown == null then
return
endif
call ReleaseTimer(this.countdown)
set this.combustPath = ""
set this.countdown = null
set this.manaBurned = 0.
set this.manaBurnAmount = 0.
set this.manaRestore = 0.
set this.range = 0.
endmethod
method combust takes unit origin returns nothing
local real prevMana
local real curMana
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(origin), GetUnitY(origin), /*
*/ this.range, null)
loop
set enumUnit = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, enumUnit)
exitwhen enumUnit == null
if filterTarget(enumUnit, origin) and BlzGetUnitMaxMana(enumUnit) > 0 then
set prevMana = GetUnitState(enumUnit, UNIT_STATE_MANA)
call SetUnitState(enumUnit, UNIT_STATE_MANA, prevMana - this.manaBurned)
set curMana = GetUnitState(enumUnit, UNIT_STATE_MANA)
set this.manaBurnAmount = this.manaBurnAmount + (prevMana - curMana)
call DestroyEffect( AddSpecialEffectTarget(this.combustPath, enumUnit, "overhead"))
call UnitDamageTarget(origin, enumUnit, prevMana - curMana, false, true, /*
*/ ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
endmethod
method detonate takes unit origin, string deathArt returns nothing
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(origin), GetUnitY(origin), /*
*/ this.range, null)
call DestroyEffect( AddSpecialEffect(deathArt, GetUnitX(origin), GetUnitY(origin)))
loop
set enumUnit = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, enumUnit)
exitwhen enumUnit == null
if filterTarget(enumUnit, origin) then
call UnitDamageTarget(origin, enumUnit, this.manaDetonate, false, true, /*
*/ ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
endmethod
endstruct
private struct SaccularHatch extends array
implement Alloc
private static timer spawnFXTimer = CreateTimer()
private static IntegerList spawnList = 0
private static Table hiveMap = 0
readonly static integer spawnData = 0
readonly static thistype spawnHatch = 0
readonly static integer deathData = 0
readonly static thistype deathHatch = 0
static string spawnArt = ""
static string deathArt = ""
private IntegerListItem spawnPointer
private BezierEasing bezier
private integer growthTick
private integer growthMaxTicks
readonly unit hive
readonly unit owner
readonly integer level
readonly IntegerList spiderList
readonly ExecuteInterface spawnCallback
readonly ExecuteInterface deathCallback
IntegerListItem pointer
integer casterData
boolean reacquired
method operator detector takes nothing returns SaccularDetector
return this
endmethod
method operator burn takes nothing returns SaccularHatchBurn
return this
endmethod
method operator roam takes nothing returns SaccularHatchRoam
return this
endmethod
// If reacquired is true, do not kill the spiders.
private method processSpiders takes nothing returns nothing
local SaccularSpider spider
set deathData = this.casterData
set deathHatch = this
call this.deathCallback.run()
if this.reacquired then
call SetUnitX(this.owner, GetUnitX(this.hive))
call SetUnitY(this.owner, GetUnitY(this.hive))
call SetUnitState(this.owner, UNIT_STATE_MANA, /*
*/ GetUnitState(this.owner, UNIT_STATE_MANA) + this.burn.manaBurnAmount*this.burn.manaRestore)
endif
loop
exitwhen this.spiderList.empty()
set spider = SaccularSpider(this.spiderList.first.data)
call this.spiderList.erase(spider.pointer)
call spider.clear(true)
endloop
endmethod
private method destroy takes nothing returns nothing
call this.processSpiders()
call this.burn.detonate(this.hive, deathArt)
call this.burn.clear()
call this.roam.clear()
call this.spiderList.destroy()
call this.deathCallback.destroy()
call this.spawnCallback.destroy()
call this.detector.flush()
call hiveMap.remove(GetHandleId(this.hive))
set this.hive = null
set this.owner = null
set this.level = 0
set this.spiderList = 0
set this.spawnCallback = 0
set this.deathCallback = 0
set this.reacquired = false
set this.casterData = 0
set this.pointer = 0
call this.deallocate()
endmethod
private static method onSpawnEffectDraw takes nothing returns nothing
local IntegerListItem iter = spawnList.first
local thistype this
local real bezOut
loop
exitwhen iter == 0
set this = thistype(iter.data)
set iter = iter.next
set this.growthTick = this.growthTick + 1
if this.growthTick > this.growthMaxTicks then
call spawnList.erase(this.spawnPointer)
set spawnData = this.casterData
set spawnHatch = this
call this.spawnCallback.run()
set this.spawnPointer = 0
set this.bezier = 0
set this.growthTick = 0
set this.growthMaxTicks = 0
else
set bezOut = this.bezier[I2R(this.growthTick) / I2R(this.growthMaxTicks)]
call SetUnitScale(this.hive, bezOut, bezOut, bezOut)
call DestroyEffect( AddSpecialEffect(spawnArt, GetUnitX(this.hive), GetUnitY(this.hive)))
endif
endloop
if spawnList.empty() then
call PauseTimer(spawnFXTimer)
endif
endmethod
private static method onManaBurn takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call this.burn.combust(this.hive)
endmethod
private static method onSpiderRoam takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if this.detector.target == null then
call this.roam.wander(this.spiderList, this.hive)
endif
endmethod
method activateManaBurn takes real manaburn, real range, real interval returns nothing
if this.burn.countdown != null then
return
endif
set this.burn.manaBurned = manaburn
set this.burn.range = range
set this.burn.countdown = NewTimerEx(this.burn)
call TimerStart(this.burn.countdown, interval, true, function thistype.onManaBurn)
endmethod
method activateRoam takes real interval returns nothing
if this.roam.countdown != null then
return
endif
set this.roam.countdown = NewTimerEx(this.roam)
call TimerStart(this.roam.countdown, interval, true, function thistype.onSpiderRoam)
endmethod
method addSpawnEffect takes real dur, real interval, BezierEasing whichEase returns nothing
if this.spawnPointer != 0 then
return
endif
set this.growthTick = 0
set this.growthMaxTicks = R2I(dur / interval + 0.5)
set this.bezier = whichEase
set this.spawnPointer = spawnList.push(this).last
if spawnList.size() == 1 then
call TimerStart(spawnFXTimer, interval, true, function thistype.onSpawnEffectDraw)
endif
endmethod
static method create takes unit owner, integer level, integer unitId, real x, real y returns thistype
local thistype this = allocate()
set this.owner = owner
set this.level = level
set this.hive = CreateUnit(GetOwningPlayer(owner), unitId, x, y, bj_UNIT_FACING)
set this.spiderList = IntegerList.create()
set this.spawnCallback = ExecuteInterface.create(null)
set hiveMap[GetHandleId(this.hive)] = this
return this
endmethod
private static method onReacquire takes nothing returns nothing
local thistype this = ReleaseTimer(GetExpiredTimer())
call KillUnit(this.hive)
endmethod
method acquire takes real delay returns nothing
if this.reacquired then
return
endif
set this.reacquired = true
call UnitPauseTimedLife(this.hive, true)
call SetUnitInvulnerable(this.hive, true)
call TimerStart(NewTimerEx(this), delay, false, function thistype.onReacquire)
endmethod
private static method onHiveDeath takes nothing returns nothing
local unit dead = GetTriggerUnit()
local integer id = GetHandleId(dead)
local thistype this
if not hiveMap.has(id) then
set dead = null
return
endif
set this = hiveMap[id]
call this.destroy()
set dead = null
endmethod
private static method initVariables takes nothing returns nothing
set spawnList = IntegerList.create()
set hiveMap = Table.create()
endmethod
private static method initTriggers takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(trig, Condition(function thistype.onHiveDeath))
endmethod
private static method onInit takes nothing returns nothing
call initVariables()
call initTriggers()
endmethod
endstruct
private struct SaccularSnap extends array
integer level
endstruct
struct SaccularGenesis extends array
implement Alloc
implement SaccularGenesisConfig
private static Table instanceMap = 0
readonly unit owner
readonly IntegerList hatchList
readonly integer level
private static method onHiveDestroy takes nothing returns nothing
local thistype this = SaccularHatch(SaccularHatch.deathData)
local SaccularHatch hatch = SaccularHatch.deathHatch
call this.hatchList.erase(hatch.pointer)
endmethod
private method initializeDetector takes SaccularHatch hatch, integer level returns nothing
call hatch.detector.initialize(HIVE_DETECT_RANGE(level))
set hatch.detector.chaseRange = SPIDER_MAX_RANGE(level)
set hatch.detector.notaryPath = MODEL_PATH(4)
set hatch.detector.spiderList = hatch.spiderList
set hatch.detector.source = hatch.hive
endmethod
private method addHiveSpiders takes SaccularHatch hatch, integer level returns nothing
local integer spiderCount = SPIDER_SPAWN_COUNT(level)
local SaccularSpider spider = 0
local real cx = GetUnitX(hatch.hive)
local real cy = GetUnitY(hatch.hive)
loop
set spider = SaccularSpider.create(this.owner, SPIDERLING_ID, cx, cy)
set spider.pointer = hatch.spiderList.push(spider).last
set spider.strRatio = SPIDER_STR_ATTACK(level)
set spider.agiRatio = SPIDER_AGI_ATTACK(level)
set spider.intRatio = SPIDER_INT_ATTACK(level)
call UnitAddAbility(spider.spider, ANTI_ATTACK_ID)
call UnitAddAbility(spider.spider, 'Aloc')
set spiderCount = spiderCount - 1
exitwhen spiderCount <= 0
endloop
endmethod
private static method onSpawnEffectEnd takes nothing returns nothing
local thistype this = SaccularHatch(SaccularHatch.spawnData)
local SaccularHatch hatch = SaccularHatch.spawnHatch
local integer level = hatch.level
call SetUnitInvulnerable(hatch.hive, false)
call UnitApplyTimedLife(hatch.hive, 'BTLF', HIVE_DURATION(level))
call hatch.activateManaBurn(MANA_BURN_FLAT(level), MANA_BURN_RANGE(level), /*
*/ MANA_BURN_INTERVAL(level))
call hatch.activateRoam(SPIDER_WANDER_INTERVAL(level))
call this.addHiveSpiders(hatch, level)
call this.initializeDetector(hatch, level)
endmethod
private static method onFinish takes Missile snap returns boolean
local thistype this = snap.data
local integer level = SaccularSnap(snap).level
local SaccularHatch hatch = SaccularHatch.create(this.owner, this.level, /*
*/ HATCHERY_ID, snap.x, snap.y)
set SaccularSnap(snap).level = 0
set hatch.pointer = this.hatchList.push(hatch).last
set hatch.casterData = this
set hatch.burn.combustPath = MODEL_PATH(1)
set hatch.burn.manaRestore = MANA_REJUVENATE_RATIO(level)
set hatch.burn.manaDetonate = HIVE_DEATH_DAMAGE(level)
set hatch.roam.range = SPIDER_ROAM_RANGE(level)
call hatch.spawnCallback.setCode(function thistype.onSpawnEffectEnd)
call hatch.deathCallback.setCode(function thistype.onHiveDestroy)
call SetUnitInvulnerable(hatch.hive, true)
call DestroyEffect(AddSpecialEffect(MODEL_PATH(0), snap.x, snap.y))
call hatch.addSpawnEffect(HIVE_GROWTH_DURATION(level), TIMER_INTERVAL, BEZIER)
return true
endmethod
implement MissileStruct
method plant takes real tx, real ty returns nothing
local Missile snap = Missile.createXYZ(GetUnitX(this.owner), GetUnitY(this.owner), /*
*/ 30, tx, ty, 0)
set SaccularSnap(snap).level = this.level
set snap.model = MODEL_PATH(2)
set snap.source = this.owner
set snap.data = this
set snap.arc = 0.4
call snap.setMovementSpeed(MISSILE_SPEED)
call launch(snap)
endmethod
static method create takes unit owner, integer level returns thistype
local integer id = GetHandleId(owner)
local thistype this = instanceMap[id]
if this == 0 then
set this = allocate()
set this.owner = owner
set this.hatchList = IntegerList.create()
set instanceMap[id] = this
call UnitAddAbility(owner, RAVAGE_ID)
call UnitMakeAbilityPermanent(owner, true, RAVAGE_ID)
endif
set this.level = level
return this
endmethod
private method searchHive takes real tx, real ty returns SaccularHatch
local IntegerListItem iter = this.hatchList.first
local SaccularHatch hatch
local SaccularHatch result = 0
local real mindist = 999999999.00
local real dist = 0.
loop
exitwhen iter == 0
set hatch = iter.data
set iter = iter.next
set dist = (GetUnitX(hatch.hive) - tx)*(GetUnitX(hatch.hive) - tx)
set dist = dist + (GetUnitY(hatch.hive) - ty)*(GetUnitY(hatch.hive) - ty)
if mindist >= dist then
set result = hatch
set mindist = dist
endif
endloop
return result
endmethod
static method onConvertHive takes unit owner, real tx, real ty returns nothing
local integer id = GetHandleId(owner)
local integer level = GetUnitAbilityLevel(owner, SPELL_ID)
local thistype this = instanceMap[id]
local SaccularHatch hatch
if this == 0 then
return
elseif this.hatchList.empty() then
return
endif
set hatch = this.searchHive(tx, ty)
call hatch.acquire(NYDUS_DELAY(level))
endmethod
private static method onInit takes nothing returns nothing
set SaccularHatch.spawnArt = MODEL_PATH(0)
set SaccularHatch.deathArt = MODEL_PATH(3)
endmethod
endstruct
private function OnSpellEffect takes nothing returns nothing
local integer abilID = GetSpellAbilityId()
local integer level
local SaccularGenesis this
local unit caster
if abilID == SaccularGenesis.SPELL_ID then
set caster = GetTriggerUnit()
set level = GetUnitAbilityLevel(caster, SaccularGenesis.SPELL_ID)
set this = SaccularGenesis.create(caster, level)
call this.plant(GetSpellTargetX(), GetSpellTargetY())
set caster = null
endif
if abilID == SaccularGenesis.RAVAGE_ID then
call SaccularGenesis.onConvertHive(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function OnSpellEffect))
endfunction
endlibrary