Name | Type | is_array | initial_value |
//TESH.scrollpos=186
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ Collection +
+ v0.6.0 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
//! textmacro Collection takes TYPE, TYPE_NAME, TYPE_CONSTANT_NAME
library $TYPE_NAME$Collection
function interface $TYPE_NAME$_Enum takes $TYPE$ this, integer data returns nothing
function interface $TYPE_NAME$_Comparator takes $TYPE$ x, $TYPE$ y returns integer
struct $TYPE_NAME$Array
private static hashtable t
private integer n
static method create takes integer n returns thistype
local thistype this = thistype.allocate()
set this.n = IMaxBJ(0, n)
return this
endmethod
method size takes nothing returns integer
return .n
endmethod
method operator [] takes integer index returns $TYPE$
if (index >= 0 and index < .n) then
return LoadInteger(.t, this, index)
endif
return 0
endmethod
method operator []= takes integer index, $TYPE$ e returns nothing
if (index >= 0 and index < .n) then
call SaveInteger(.t, this, index, e)
endif
endmethod
method destroy takes nothing returns nothing
call FlushChildHashtable(this.t, this)
call this.deallocate()
endmethod
implement optional $TYPE_NAME$ArraySort
implement optional $TYPE_NAME$ArrayStableSort
implement optional $TYPE_NAME$ArraySelection
implement optional $TYPE_NAME$ArrayBinarySearch
private static method onInit takes nothing returns nothing
set .t = InitHashtable()
endmethod
endstruct
private interface CollectionInterface
method add takes $TYPE$ toAdd returns boolean defaults false
method addAll takes $TYPE_NAME$Collection collection returns nothing defaults nothing
method remove takes $TYPE$ toRemove returns boolean defaults false
method removeAll takes $TYPE_NAME$Collection collection returns nothing defaults nothing
method retainAll takes $TYPE_NAME$Collection collection returns nothing defaults nothing
method contains takes $TYPE$ element returns boolean defaults false
method size takes nothing returns integer defaults 0
method isEmpty takes nothing returns boolean defaults false
method clear takes nothing returns nothing defaults nothing
method toArray takes nothing returns $TYPE_NAME$Array defaults 0
method iterator takes nothing returns $TYPE_NAME$Iterator defaults 0
method enum takes $TYPE_NAME$_Enum enum, integer data returns nothing defaults nothing
endinterface
interface $TYPE_NAME$Iterator
method next takes nothing returns $TYPE$
method hasNext takes nothing returns boolean
method remove takes nothing returns nothing
endinterface
struct $TYPE_NAME$Collection extends CollectionInterface
implement optional $TYPE_NAME$CollectionSelection
stub method addAll takes $TYPE_NAME$Collection collection returns nothing
call this.addAllHelper.evaluate(collection)
endmethod
stub method removeAll takes $TYPE_NAME$Collection collection returns nothing
call this.removeAllHelper.evaluate(collection)
endmethod
stub method retainAll takes $TYPE_NAME$Collection collection returns nothing
call this.retainAllHelper.evaluate(collection)
endmethod
private method addAllHelper takes $TYPE_NAME$Collection collection returns nothing
local $TYPE_NAME$Iterator it = collection.iterator()
loop
exitwhen (it.hasNext() == false)
call this.add(it.next())
endloop
call it.destroy()
endmethod
private method removeAllHelper takes $TYPE_NAME$Collection collection returns nothing
local $TYPE_NAME$Iterator it = this.iterator()
loop
exitwhen (it.hasNext() == false)
if (collection.contains(it.next())) then
call it.remove()
endif
endloop
call it.destroy()
endmethod
private method retainAllHelper takes $TYPE_NAME$Collection collection returns nothing
local $TYPE_NAME$Iterator it = this.iterator()
loop
exitwhen (it.hasNext() == false)
if (collection.contains(it.next()) == false) then
call it.remove()
endif
endloop
call it.destroy()
endmethod
endstruct
endlibrary
library $TYPE_NAME$Set requires $TYPE_NAME$Collection
struct $TYPE_NAME$Set extends $TYPE_NAME$Collection
endstruct
endlibrary
library $TYPE_NAME$List requires $TYPE_NAME$Collection
struct $TYPE_NAME$ListTypeIterator extends $TYPE_NAME$Iterator
stub method next takes nothing returns $TYPE$
return 0
endmethod
stub method hasNext takes nothing returns boolean
return false
endmethod
stub method nextIndex takes nothing returns integer
return 0
endmethod
stub method previous takes nothing returns $TYPE$
return 0
endmethod
stub method hasPrevious takes nothing returns boolean
return false
endmethod
stub method previousIndex takes nothing returns integer
return 0
endmethod
stub method add takes $TYPE$ toAdd returns nothing
endmethod
stub method remove takes nothing returns nothing
endmethod
stub method setElement takes $TYPE$ toSet returns nothing
endmethod
endstruct
struct $TYPE_NAME$List extends $TYPE_NAME$Collection
stub method addByIndex takes integer index, $TYPE$ toAdd returns nothing
endmethod
stub method setByIndex takes integer index, $TYPE$ toSet returns $TYPE$
return 0
endmethod
stub method getByIndex takes integer index returns $TYPE$
return 0
endmethod
stub method removeByIndex takes integer index returns $TYPE$
return 0
endmethod
stub method swap takes integer index1, integer index2 returns nothing
endmethod
stub method indexOf takes $TYPE$ element returns integer
return -1
endmethod
stub method listIteratorByIndex takes integer index returns $TYPE_NAME$ListTypeIterator
return 0
endmethod
stub method listIterator takes nothing returns $TYPE_NAME$ListTypeIterator
return .listIteratorByIndex(0)
endmethod
implement optional $TYPE_NAME$ListSort
implement optional $TYPE_NAME$ListStableSort
implement optional $TYPE_NAME$ListSelection
implement optional $TYPE_NAME$ListBinarySearch
endstruct
endlibrary
//! endtextmacro
//TESH.scrollpos=488
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ TreeSet +
+ v0.6.0 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
//! textmacro TreeSet takes TYPE, TYPE_NAME, TYPE_CONSTANT_NAME
library $TYPE_NAME$TreeSet requires $TYPE_NAME$Set
private keyword $TYPE_NAME$TreeSetIterator
private struct $TYPE_NAME$TreeSetNode
static constant integer BLACK = 0
static constant integer RED = 1
static thistype sentinelNode
$TYPE_NAME$TreeSet s
$TYPE$ e
thistype parent
thistype left
thistype right
integer color
static method create takes $TYPE_NAME$TreeSet s, integer color, $TYPE$ e, thistype parent, boolean sentinel returns thistype
local thistype this = thistype.allocate()
set this.s = s
set this.color = color
set this.e = e
set this.parent = parent
if (sentinel == false) then
set this.left = thistype.sentinelNode //thistype.create(s, thistype.BLACK, 0, this, true)
set this.right = thistype.sentinelNode //thistype.create(s, thistype.BLACK, 0, this, true)
endif
return this
endmethod
method operator sentinel takes nothing returns boolean
return this == thistype.sentinelNode
endmethod
method operator sibling takes nothing returns thistype
if (.parent != 0) then
if (this == .parent.left) then
return .parent.right
else
return .parent.left
endif
endif
return 0
endmethod
method operator grandparent takes nothing returns thistype
if (.parent != 0) then
return .parent.parent
endif
return 0
endmethod
method operator uncle takes nothing returns thistype
local thistype grandparent = .grandparent
if (grandparent != 0) then
if (.parent == grandparent.left) then
return grandparent.right
else
return grandparent.left
endif
endif
return 0
endmethod
method rotateLeft takes nothing returns nothing
if (.parent != 0) then
if (this == .parent.left) then
set .parent.left = .right
else
set .parent.right = .right
endif
endif
set .right.parent = .parent
set .parent = .right
set .right = .parent.left
set .parent.left = this
if (.right.sentinel == false) then
set .right.parent = this
endif
if (this == .s.root) then
set .s.root = .parent
endif
endmethod
method rotateRight takes nothing returns nothing
if (.parent != 0) then
if (this == .parent.left) then
set .parent.left = .left
else
set .parent.right = .left
endif
endif
set .left.parent = .parent
set .parent = .left
set .left = .parent.right
set .parent.right = this
if (.left.sentinel == false) then
set .left.parent = this
endif
if (this == .s.root) then
set .s.root = .parent
endif
endmethod
private method inOrderSuccessorHelper takes nothing returns $TYPE_NAME$TreeSetNode
if (this.sentinel) then
return 0
elseif (this.left.sentinel) then
return this
endif
return this.left.inOrderSuccessorHelper()
endmethod
method getInOrderSuccessor takes nothing returns $TYPE_NAME$TreeSetNode
if (this.sentinel) then
return 0
endif
return this.right.inOrderSuccessorHelper.evaluate()
endmethod
private static method onInit takes nothing returns nothing
set thistype.sentinelNode = thistype.create(0, thistype.BLACK, 0, 0, true)
endmethod
endstruct
struct $TYPE_NAME$TreeSet extends $TYPE_NAME$Set
$TYPE_NAME$TreeSetNode root = 0
integer n = 0
$TYPE_NAME$_Comparator comparator = 0
static method create takes $TYPE_NAME$_Comparator comparator returns thistype
local thistype this = thistype.allocate()
set this.comparator = comparator
return this
endmethod
method add takes $TYPE$ toAdd returns boolean
if (this.root == 0) then
set this.root = $TYPE_NAME$TreeSetNode.create(this, $TYPE_NAME$TreeSetNode.BLACK, toAdd, 0, false)
else
if (this.addNode.evaluate(toAdd, this.root) == false) then
return false
endif
endif
set this.n = this.n + 1
return true
endmethod
method remove takes $TYPE$ toRemove returns boolean
local $TYPE_NAME$TreeSetNode node = this.findNode.evaluate(toRemove, this.root)
if (node != 0) then
call this.removeNode.evaluate(node)
set this.n = this.n - 1
return true
endif
return false
endmethod
method contains takes $TYPE$ element returns boolean
return this.findNode.evaluate(element, this.root) != 0
endmethod
method size takes nothing returns integer
return this.n
endmethod
method isEmpty takes nothing returns boolean
return this.n == 0
endmethod
method clear takes nothing returns nothing
call this.clearHelper.evaluate(this.root)
set this.root = 0
set this.n = 0
endmethod
method toArray takes nothing returns $TYPE_NAME$Array
return this.toArrayHelper.evaluate()
endmethod
method iterator takes nothing returns $TYPE_NAME$Iterator
return $TYPE_NAME$TreeSetIterator.create.evaluate(this)
endmethod
method enum takes $TYPE_NAME$_Enum enum, integer data returns nothing
if (this.n == 0) then
return
endif
call this.enumHelper.evaluate(this.root, enum, data)
endmethod
method destroy takes nothing returns nothing
call this.clear()
call this.deallocate()
endmethod
private stub method addNode takes $TYPE$ element, $TYPE_NAME$TreeSetNode node returns boolean
local integer order
if (this.comparator != 0) then
set order = this.comparator.evaluate(element, node.e)
else
if (element < node.e) then
set order = -1
elseif (element > node.e) then
set order = 1
else
set order = 0
endif
endif
if (order < 0) then
if (node.left.sentinel) then
set node.left = $TYPE_NAME$TreeSetNode.create(this, $TYPE_NAME$TreeSetNode.RED, element, node, false)
return true
else
return this.addNode(element, node.left)
endif
elseif (order > 0) then
if (node.right.sentinel) then
set node.right = $TYPE_NAME$TreeSetNode.create(this, $TYPE_NAME$TreeSetNode.RED, element, node, false)
return true
else
return this.addNode(element, node.right)
endif
endif
return false
endmethod
private method findNode takes $TYPE$ element, $TYPE_NAME$TreeSetNode node returns $TYPE_NAME$TreeSetNode
local integer order
if (node == 0) then
return 0
elseif (node.sentinel) then
return 0
endif
if (this.comparator != 0) then
set order = this.comparator.evaluate(element, node.e)
else
if (element < node.e) then
set order = -1
elseif (element > node.e) then
set order = 1
else
set order = 0
endif
endif
if (order < 0) then
return this.findNode(element, node.left)
elseif (order > 0) then
return this.findNode(element, node.right)
else
return node
endif
endmethod
private stub method removeNode takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode successor
if (node.right.sentinel == false and node.left.sentinel == false) then
set successor = node.getInOrderSuccessor()
set node.e = successor.e
call this.removeNode.evaluate(successor)
elseif (node.right.sentinel and node.left.sentinel) then
if (node != this.root) then
if (node == node.parent.left) then
set node.parent.left = $TYPE_NAME$TreeSetNode.sentinelNode
else
set node.parent.right = $TYPE_NAME$TreeSetNode.sentinelNode
endif
else
set this.root = 0
endif
call node.destroy()
else
if (node.right.sentinel) then
if (node.parent.left == node) then
set node.parent.left = node.left
else
set node.parent.right = node.left
endif
set node.left.parent = node.parent
if (node == this.root) then
set this.root = node.left
endif
else
if (node.parent.left == node) then
set node.parent.left = node.right
else
set node.parent.right = node.right
endif
set node.right.parent = node.parent
if (node == this.root) then
set this.root = node.right
endif
endif
call node.destroy()
endif
endmethod
private method clearHelper takes $TYPE_NAME$TreeSetNode node returns nothing
if (node == 0) then
return
endif
if (node.left.sentinel == false) then
call this.clearHelper(node.left)
endif
if (node.right.sentinel == false) then
call this.clearHelper(node.right)
endif
call node.destroy()
endmethod
private method enumHelper takes $TYPE_NAME$TreeSetNode node, $TYPE_NAME$_Enum enum, integer data returns nothing
if (node.left.sentinel == false) then
call this.enumHelper(node.left, enum, data)
endif
call enum.evaluate(node.e, data)
if (node.right.sentinel == false) then
call this.enumHelper(node.right, enum, data)
endif
endmethod
private method toArrayHelper takes nothing returns $TYPE_NAME$Array
local $TYPE_NAME$TreeSetNode node = this.root
local $TYPE_NAME$Array ar
local integer n = 0
if (node == 0) then
return 0
endif
set ar = $TYPE_NAME$Array.create(this.size())
loop
exitwhen (node.left.sentinel)
set node = node.left
endloop
loop
set ar[n] = node.e
set n = n + 1
if (node.right.sentinel == false) then
set node = node.right
loop
exitwhen (node.left.sentinel)
set node = node.left
endloop
else
loop
exitwhen (node.parent == 0 or node != node.parent.right)
set node = node.parent
endloop
set node = node.parent
endif
exitwhen (node.sentinel or node == 0)
endloop
return ar
endmethod
endstruct
struct $TYPE_NAME$BalancedTreeSet extends $TYPE_NAME$TreeSet
private method addNode takes $TYPE$ element, $TYPE_NAME$TreeSetNode node returns boolean
local integer order
if (this.comparator != 0) then
set order = this.comparator.evaluate(element, node.e)
else
if (element < node.e) then
set order = -1
elseif (element > node.e) then
set order = 1
else
set order = 0
endif
endif
if (order < 0) then
if (node.left.sentinel) then
set node.left = $TYPE_NAME$TreeSetNode.create(this, $TYPE_NAME$TreeSetNode.RED, element, node, false)
call this.insertCaseOne.evaluate(node.left)
return true
else
return this.addNode(element, node.left)
endif
elseif (order > 0) then
if (node.right.sentinel) then
set node.right = $TYPE_NAME$TreeSetNode.create(this, $TYPE_NAME$TreeSetNode.RED, element, node, false)
call this.insertCaseOne.evaluate(node.right)
return true
else
return this.addNode(element, node.right)
endif
endif
return false
endmethod
private method removeNode takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode successor
if (node.right.sentinel == false and node.left.sentinel == false) then
set successor = node.getInOrderSuccessor()
set node.e = successor.e
call this.removeNode.evaluate(successor)
elseif (node.right.sentinel and node.left.sentinel) then
if (node != this.root) then
if (node == node.parent.left) then
set node.parent.left = $TYPE_NAME$TreeSetNode.sentinelNode
else
set node.parent.right = $TYPE_NAME$TreeSetNode.sentinelNode
endif
else
set this.root = 0
endif
call node.destroy()
else
if (node.color == $TYPE_NAME$TreeSetNode.RED) then
if (node.right.sentinel) then
if (node.parent.left == node) then
set node.parent.left = node.left
else
set node.parent.right = node.left
endif
set node.left.parent = node.parent
if (node == this.root) then
set this.root = node.left
endif
else
if (node.parent.left == node) then
set node.parent.left = node.right
else
set node.parent.right = node.right
endif
set node.right.parent = node.parent
if (node == this.root) then
set this.root = node.right
endif
endif
else
if (node.right.sentinel) then
if (node.parent.left == node) then
set node.parent.left = node.left
else
set node.parent.right = node.left
endif
set node.left.parent = node.parent
if (node == this.root) then
set this.root = node.left
endif
if (node.left.color == $TYPE_NAME$TreeSetNode.RED) then
set node.left.color = $TYPE_NAME$TreeSetNode.BLACK
else
call this.removeCaseOne.evaluate(node.left)
endif
else
if (node.parent.left == node) then
set node.parent.left = node.right
else
set node.parent.right = node.right
endif
set node.right.parent = node.parent
if (node == this.root) then
set this.root = node.right
endif
if (node.right.color == $TYPE_NAME$TreeSetNode.RED) then
set node.right.color = $TYPE_NAME$TreeSetNode.BLACK
else
call this.removeCaseOne.evaluate(node.right)
endif
endif
endif
call node.destroy()
endif
endmethod
private method insertCaseFive takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode grandparent = node.grandparent
set node.parent.color = $TYPE_NAME$TreeSetNode.BLACK
set grandparent.color = $TYPE_NAME$TreeSetNode.RED
if (node == node.parent.left and node.parent == grandparent.left) then
call grandparent.rotateRight()
else
call grandparent.rotateLeft()
endif
endmethod
private method insertCaseFour takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode grandparent = node.grandparent
if (node == node.parent.right and node.parent == grandparent.left) then
call node.parent.rotateLeft()
set node = node.left
elseif (node == node.parent.left and node.parent == grandparent.right) then
call node.parent.rotateRight()
set node = node.right
endif
call this.insertCaseFive(node)
endmethod
private method insertCaseThree takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode uncle = node.uncle
local $TYPE_NAME$TreeSetNode grandparent = node.grandparent
if (uncle != 0) then
if (uncle.color == $TYPE_NAME$TreeSetNode.RED) then
set node.parent.color = $TYPE_NAME$TreeSetNode.BLACK
set uncle.color = $TYPE_NAME$TreeSetNode.BLACK
set grandparent.color = $TYPE_NAME$TreeSetNode.RED
call this.insertCaseOne.evaluate(grandparent)
else
call this.insertCaseFour(node)
endif
else
call this.insertCaseFour(node)
endif
endmethod
private method insertCaseTwo takes $TYPE_NAME$TreeSetNode node returns nothing
if (node.parent.color != $TYPE_NAME$TreeSetNode.BLACK) then
call this.insertCaseThree(node)
endif
endmethod
private method insertCaseOne takes $TYPE_NAME$TreeSetNode node returns nothing
if (node.parent == 0) then
set node.color = $TYPE_NAME$TreeSetNode.BLACK
else
call this.insertCaseTwo(node)
endif
endmethod
private method removeCaseSix takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode sibling = node.sibling
set sibling.color = node.parent.color
set node.parent.color = $TYPE_NAME$TreeSetNode.BLACK
if (node == node.parent.left) then
set sibling.right.color = $TYPE_NAME$TreeSetNode.BLACK
call node.parent.rotateLeft()
else
set sibling.left.color = $TYPE_NAME$TreeSetNode.BLACK
call node.parent.rotateRight()
endif
endmethod
private method removeCaseFive takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode sibling = node.sibling
if (sibling.color == $TYPE_NAME$TreeSetNode.BLACK) then
if (node == node.parent.left and sibling.right.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.left.color == $TYPE_NAME$TreeSetNode.RED) then
set sibling.color = $TYPE_NAME$TreeSetNode.RED
set sibling.left.color = $TYPE_NAME$TreeSetNode.BLACK
call sibling.rotateRight()
elseif (node == node.parent.right and sibling.left.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.right.color == $TYPE_NAME$TreeSetNode.RED) then
set sibling.color = $TYPE_NAME$TreeSetNode.RED
set sibling.right.color = $TYPE_NAME$TreeSetNode.BLACK
call sibling.rotateLeft()
endif
endif
call this.removeCaseSix(node)
endmethod
private method removeCaseFour takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode sibling = node.sibling
if (node.parent.color == $TYPE_NAME$TreeSetNode.RED and sibling.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.left.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.right.color == $TYPE_NAME$TreeSetNode.BLACK) then
set sibling.color = $TYPE_NAME$TreeSetNode.RED
set node.parent.color = $TYPE_NAME$TreeSetNode.BLACK
else
call this.removeCaseFive(node)
endif
endmethod
private method removeCaseThree takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode sibling = node.sibling
if (node.parent.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.left.color == $TYPE_NAME$TreeSetNode.BLACK and sibling.right.color == $TYPE_NAME$TreeSetNode.BLACK) then
set sibling.color = $TYPE_NAME$TreeSetNode.RED
call this.removeCaseOne.evaluate(node.parent)
else
call this.removeCaseFour(node)
endif
endmethod
private method removeCaseTwo takes $TYPE_NAME$TreeSetNode node returns nothing
local $TYPE_NAME$TreeSetNode sibling = node.sibling
if (sibling.color == $TYPE_NAME$TreeSetNode.RED) then
set node.parent.color = $TYPE_NAME$TreeSetNode.RED
set sibling.color = $TYPE_NAME$TreeSetNode.BLACK
if (node == node.parent.left) then
call node.parent.rotateLeft()
else
call node.parent.rotateRight()
endif
endif
call this.removeCaseThree(node)
endmethod
private method removeCaseOne takes $TYPE_NAME$TreeSetNode node returns nothing
if (node.parent != 0) then
call this.removeCaseTwo(node)
endif
endmethod
endstruct
struct $TYPE_NAME$TreeSetIterator extends $TYPE_NAME$Iterator
private $TYPE_NAME$TreeSet s
private $TYPE_NAME$Array ar
private integer n
private integer last = -1
private integer current = 0
static method create takes $TYPE_NAME$TreeSet s returns thistype
local thistype this = thistype.allocate()
set this.s = s
set this.ar = s.toArray()
set this.n = s.n
return this
endmethod
method hasNext takes nothing returns boolean
return this.current < this.n
endmethod
method next takes nothing returns $TYPE$
if (this.hasNext()) then
set this.last = this.current
set this.current = this.current + 1
return this.ar[this.last]
endif
return 0
endmethod
method remove takes nothing returns nothing
if (this.last != -1) then
call this.s.remove(this.ar[this.last])
set this.last = -1
endif
endmethod
method destroy takes nothing returns nothing
call this.ar.destroy()
call this.deallocate()
endmethod
endstruct
endlibrary
//! endtextmacro
//TESH.scrollpos=59
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=285
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
========================================= Damage Engine =========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
------------------------------------------ Created by: ------------------------------------------
------------------------------------------ aznricepuff ------------------------------------------
Damage Engine is an object-oriented event-driven damage detection system. Ever wanted to have
triggered evasion? Critical strikes? Blocks? Ever wanted to have a damage display system? Ever
wanted to have an easy way to make custom armor and damage types? If so, Damage Engine is your
answer!
======================================== Implementation =========================================
First off, lets start with how to implement this system:
Requirements
------------
- jasshelper (Demo Map: 0.9.H.0 or later)
- Object Merger (by grimoire)
- Table (by Vexorian)
- TimerUtils (by Vexorian)
- TreeSet
This system requires jasshelper (The demo map requires 0.9.H.0+ because the code has delimited
comments) and Object Merger (part of the grimext pack, which in turn is part of JNGP) as well as
the 3 libraries listed above. Get jasshelper and Object Merger (either by themselves or through
JNGP) and implement Table, TimerUtils, and TreeSet before attempting to implement Damage Engine.
Additionally, this system requires 2 abilities:
- Damage Detection Ability
- Damage Modification Ability
and 1 buff:
- Damage Detection Buff
Theres no need to copy-paste these abilities into your map. Once you have the Damage Engine
libraries in your map, just save the map once. During the jasshelper stage, it should pause for
a bit while saying "Executing external commands". After you save your map once, you can comment
out the three external Object Merger lines in the trigger Damage Engine just below the header.
========================================== Limitations ==========================================
Because Damage Engine uses an orb ability to differentiate between spell/triggered damage and
attack damage, you should not give any units in a map implementing this system any orb abilities
or arrow abilities.
============================================ A Note =============================================
The remaining sections in this documentation are meant for users with only a basic understanding
of object-oriented event-driven programming. More advanced users may wish to refer directly
to the API, which contains all the pertinent information in this documentation as well as more
technical specifications of the components of this system.
========================================== Setting Up ===========================================
The heart of Damage Engine is the struct DamageEngine. This struct handles almost all of the
system's tasks. It has several many static methods, but we will just focus on two for now:
------
private static method onUnitEntersMap takes unit u returns DamageEntity
private static method onUnitUpgrade takes unit u returns DamageEntity
------
This system requires that every unit on your map have its own instance of the DamageEntity
struct. Because of this, the system assigns every unit that appears on the map or is upgraded an
instance of DamageEntity by default.
These methods are called whenever a unit enters the map or when a unit finishes an upgrade.
They both take one parameter, u, which is the unit that entered the map or finished the upgrade.
They also both return an instance of DamageEntity, which is the instance that will be assigned
to the unit. By default, DamageEngine.onUnitEntersMap() always returns a new instance of
DamageEntity and DamageEngine.onUnitUpgrade() always returns the same instance of DamageEntity
that was already assigned to the unit before it was upgraded. Note that these two functions MUST
return a valid instance of DamageEntity (NOT 0) or else the system will not work correctly.
========================================= Damage Entity =========================================
So what does DamageEntity do? It controls damage manipulation done by Damage Engine through
an event-driven system. DamageEntity contains four event response methods:
------
stub method onDamage takes EventDamage eventDamage returns nothing
stub method onDamaged takes EventDamage eventDamage returns nothing
stub method onFinalDamage takes EventDamage eventDamage returns nothing
stub method onFinalDamaged takes EventDamage eventDamage returns nothing
------
When the system detects damage being dealt, it will find the struct instances associated with
the units involved and call these methods on those instances.
All four methods have a single parameter: EventDamage eventDamage. The EventDamage instance
passed to this parameter contains all the information related to the event damage. A quick
overview of what members EventDamage contains:
------
readonly DamageEntity attacker - the DamageEntity of the attacking unit.
readonly DamageEntity target - the DamageEntity of the damaged unit.
real damage - the current amount of damage dealt.
readonly real oldDamage - the previous amount of damage dealt before the last modification to
EventDamage.damage
readonly real originalDamage - the original amount of damage dealt (before any modification)
boolean spell - true if the damage was dealt by a spell or was triggered; false if it was dealt
by an attack
integer data - the event data associated with the damage (see the Damager documentation)
delegate DamageType - the damage type of the damage (see the DamageType documentation)
------
With the exception of EventDamage.attacker and EventDamage.target, you may modify any of the
above fields as you want. By modifying the fields, you are in effect modifying the damage. For
example, by changing EventDamage.damage, you are changing the amount of damage being done, so
if you set it equal to 0, the system will modify the damage to 0, and if you set it equal to
9999.9, the system will modify the damage to 9999.9. This way, you can modify damage values
without having to make endless calls to SetWidgetLife() or UnitDamageTarget().
A note on the delegate DamageType. For those who are unfamiliar with delegate, it is an OOP
concept whereby one object delegates or defers certain tasks to another object. In practice,
this just means that an object can behave as another type of object without resorting to full
inheritance. In the case of EventDamage, this means you can call any method that exists for
DamageType on instances of EventDamage as well (for more information on DamageType, see the
DamageType documentation). So you can do the following:
------
if (eventDamage.hasFlag(TYPE_PHYSICAL)) then
call eventDamage.removeFlag(TYPE_PHYSICAL)
call eventDamage.addFlag(TYPE_ARCANE)
endif
------
You can therefore modify the damage properties of event damage this way.
One important thing that is absolutely imperative to remember is:
**************************************
NEVER CALL .destroy() ON EVENTDAMAGE!*
**************************************
Doing so will seriously bug up the system. Damage Engine automatically cleans up EventDamage
instances, so there is no reason to ever call EventDamage.destroy().
It is important to note that for two of these methods: DamageEntity.onFinalDamage() and
DamageEntity.onFinalDamaged() are called after damage has been manipulated and dealt. They are
only called when a unit actually suffers non-zero damage. These methods cannot manipulate
damage, and are included in case you want to execute actions specifically when a unit suffers
damage after all manipulations have been taken into account. (You can still modify the members
in eventDamage, but it will have no effect.)
Besides the event-response methods, DamageEntity has some other members that are important:
------
readonly unit u
stub method getDamageType takes DamageEntity target, boolean spell returns DamageType
------
The read-only instance field u points to the unit that the DamageEntity instance is associated
with.
The method getDamageType() returns the DamageType instance associated with the damage dealt by
the DamageEntity's unit when the unit deals damage. By default this method returns a generic
DamageType instance with no flags added. You may override this method in child structs. See the
Customization section for more information.
DamageEntity instances must be registered with DamageEngine before they can have any effect.
Instances that are returned by DamageEngine.onUnitEntersMap() and DamageEngine.onUnitUpgrade()
are automatically registered. Otherwise, you may use the following method in DamageEngine:
------
static method registerDamageEntity takes DamageEntity damageEntity returns nothing
------
The above method will register the given DamageEntity and tie it to the unit returned by
damageEntity.u. If another DamageEntity was previously registered to that unit, it will be
automatically destroyed.
You can also unregister DamageEntity instances with another method in DamageEngine:
------
static method removeDamageEntity takes DamageEntity damageEntity returns nothing
------
Because the system requires that all units have a DamageEntity instance tied to them at all
times, the system will automatically create a default DamageEntity instance, register it and tie
it to the appropriate unit when this method is called.
======================================== Damage Listener ========================================
While DamageEntity is great for damage manipulation for specific unit types, what if you
wanted to manipulate or detect damage regardless of what unit dealt or received the damage? In
this case DamageListener is the solution.
DamageListener is another struct type that "listens" for all damage events. It has two event-
response methods:
------
method onDamage takes EventDamage eventDamage returns nothing
method onFinalDamage takes EventDamage eventDamage returns nothing
------
These behave similarly to DamageEntity's event-response methods but are universal;
DamageListener.onDamage() is called whenever any unit receives potential damage and
DamageListener.onFinalDamage() is called whenever any unit suffers damage after all
manipulations.
Instances of DamageListener, unlike DamageEntity, are not tied to a specific unit, but they
still must be registered with DamageEngine to have any effect. To do this, use the following
method in DamageEngine:
------
static method registerDamageListener takes DamageListener damageListener returns boolean
------
It will return true if the listener was successfully registered.
You can also unregister a DamageListener:
------
static method removeDamageListener takes DamageListener damageListener returns boolean
------
It will return true if the listener was registered and successfully unregistered.
You also have the ability to specify in what order the registered DamageListeners are called
on damage events. This is done through the DamageListener's priority field. The lower the
integer in this field, the higher the priority. You specify the priority when you instantiate
a DamageListener. The highest priority listeners are called first. If two registered listeners
have equal priorities, then their execution order with respect to each other is undefined.
For more information on event-response execution order, refer to the next section.
======================================== Execution Order ========================================
One important thing to keep in mind is order of execution. Event-response methods are executed
in a set order:
1. DamageEntity.onDamage()
2. DamageEntity.onDamaged()
3. DamageListener.onDamage()*
4. DamageEntity.onFinalDamage()
5. DamageEntity.onFinalDamaged()
6. DamageListener.onFinalDamage()*
* Individual listener execution order is based on listener priorities (see above section).
This has important implications. Probably the foremost is that if any method sets the event
damage value to 0, none of the subsequent methods will even be called. Another example is if you
change the EventDamage.spell field in a DamageListener event-response method. Because listeners
are called after DamageEntitys, DamageEntity.onDamage() and DamageEntity.onDamaged() would not
have been aware of the change (because at the time of their invocation, the change had not yet
been made!).
========================================= Miscellaneous =========================================
DamageEngine has some other static methods that may be useful:
------
static method isEnabled takes nothing returns boolean
static method enable takes boolean b returns nothing
static method getKiller takes nothing returns unit
static method unitDamageTarget takes unit attacker, unit target, real damage, Damager damager
returns real
------
The static methods isEnabled() and enable() allow you to detect/control whether the damage
detection portion of the system is on or not. If DamageEngine.isEnabled() == false, then the
system will not detect or manipulate damage.
DamageEngine.getDamageEntity() returns the DamageEntity instance associated with the given
unit.
DamageEngine.getKiller() can be accessed inside any trigger action that responds to the
events:
------
EVENT_PLAYER_UNIT_DEATH
EVENT_UNIT_DEATH
------
You should use DamageEngine.getKiller() in place of the native GetKillingUnit() since
GetKillingUnit() will not always work correctly because of the way this system manipulates
damage.
DamageEngine.unitDamageTarget() is used to execute triggered damage. For more information
on this, refer to the Damager documentation.
For more information on any of these members, refer to the API documentation.
======================================== Customization ==========================================
By default, none of the event response methods in DamageEntity do much. Nothing happens when
damage is dealt and no manipulation of damage is ever done. So how do you make this system
actually DO something?
There are two ways to customize how damage events are handled. The first is to extend
DamageEntity with your own struct types that override DamageEntity.onDamage(),
DamageEntity.onDamaged(), DamageEntity.onFinalDamage(), DamageEntity.onFinalDamage(). Then,
change the code in DamageEngine.onUnitEntersMap() and DamageEngine.onUnitUpgrade() to return
instances of the appropriate struct type based on the unit instead of just always returning
instances of the default DamageEntity struct. This way, when the system detects that a unit
damages another, it will find instances of your struct types associated with those units and
call the methods defined in your struct types instead of calling the methods in DamageEntity,
which do nothing.
The second is to create and register DamageListener instances. Remember that the methods in
DamageListener respond to ALL damage events.
To see how this is done and to get an idea of what kind of things are possible using this
system, refer to the triggers under the "Examples" folder.
//TESH.scrollpos=54
//TESH.alwaysfold=0
======================================= Triggered Damage ========================================
Damage Engine not only offers functionality to manipulate event damage, it also provides a
comprehensive functionality for dealing triggered damage. The event response portion of
this system in no way requires you to also exclusively use the system when dealing triggered
damage. You may make direct calls to UnitDamageTarget() or use any other damage system (like
xedamage) and Damage Engine will work just fine. This part of the system is just included for
convenience.
Setting up this part of the system only requires changing one constant:
------
static constant real MAX_COLLISION_SIZE = 200.0
------
This is inside the Configuration section of Damager and should be set to the maximum collision
size of any unit in your map.
Damage Engine deals triggered damage mainly through the struct type Damager. An instance of
Damager can be viewed as a single damage source. Using Damager is much like using DamageEntity;
you create your own struct types that extend Damager. Once you have created your own struct
types, you may work with those directly instead of using Damager to deal triggered damage.
All struct types that extend Damager inherit the following instance members:
------
attacktype aType = ATTACK_TYPE_NORMAL
damagetype dType = DAMAGE_TYPE_UNIVERSAL
weapontype wType = WEAPON_TYPE_WHOKNOWS
integer data = 0
method damageTarget takes unit attacker, unit target, real damage returns real
method damageAOE takes unit attacker, real x, real y, real radius, real damage returns nothing
method damageGroup takes unit attacker, group targets, real damage returns nothing
------
The first three fields are the standard attack, damage, and weapon types that the
UnitDamageTarget() and UnitDamagePoint() natives use. The default values are shown above. You
can set these to any of the blizz constants to get the desired effect. For example, if you
want your damager to deal siege damage instead:
set damager.aType = ATTACK_TYPE_SIEGE
The field data can hold any integer (which means any struct type), and can be modified and
accessed at will. Whatever information is contained in a damagers data field when the damager
deals damaged will be passed to the event response methods that are called through the parameger
eventData. This is basically a way for a damager to pass information to the event response
methods.
The three methods damageTarget(), damageAOE(), and damageGroup() should be self-explanatory. One
thing to note is that damageTarget() returns the ACTUAL damage that was dealt to the unit, which
is not necessarily the damage value passed to the method parameters if you manipulate damage
using any of the event response methods provided by this system.
In addition to the members already discussed, struct types that extend Damager also inherit and
have the option to override the following four methods:
------
stub method getDamageType takes unit attacker, unit target returns DamageType
stub method isTarget takes unit attacker, unit target returns boolean
stub method onDamage takes EventDamage eventDamage returns nothing
stub method onFinalDamage takes EventDamage eventDamage returns nothing
------
Damager.getDamageType() should return the DamageType instance associated with the damage
dealt by your damager when the damager deals damage. The instance that is returned will be
passed to the event response methods through the parameter eventDamageType. By default this
method returns a generic DamageType instance with no flags added. For more information on the
DamageType struct, refer to the DamageType documentation.
Damager.isTarget() is called whenever a damager attempts to damage a unit and determines if
the specified target is a valid one for the specified attacker. If the method returns true, the
damage will be dealt; if it returns false no damage will be dealt. By default this method
returns true.
The other two methods are typical event response methods that work the same as the similarly-
named methods in DamageEntity. Damager.onDamage() is called when the damager deals damage. Note
that this method is called in addition to the event response methods in DamageEntity and will
be called before those methods and any DamageListeners. Damager.onFinalDamage() is called when
a damager actually deals non-zero damage and will be called in addition to and after any
DamageEntity and DamageListener methods.
For examples of how Damager is used, refer to the triggers under the "Examples" folder.
//TESH.scrollpos=0
//TESH.alwaysfold=0
========================================== Damage Type ============================================
Damage Type is just a small add-on to Damage Engine meant to abstract away some of the low-level
stuff when dealing with damage types. It is a pseudo-bitfield implementation (I say pseudo
because JASS doesnt actually support bitwise operations) and its so simple it consists of one
struct type and three instance methods.
The heart of this add-on is the DamageType struct, which is meant to represent (what else?) a
type of damage. To create your own damage type, simply instantiate a new DamageType. You may
then call one of three methods on your instance:
------
method hasFlag takes integer flag returns boolean
method addFlag takes integer flag returns nothing
method removeFlag takes integer flag returns nothing
------
First, lets talk about the addFlag() method. This method is used to add a predefined flag, meant
to represent a certain property of damage, to your DamageType instance. The sole parameter takes
in the integer equivalent of the bitflag. If you are unfamiliar with bitflags and bitfields,
here is a short explanation of how they work:
Bitfields are just a way to store an array of boolean bitflags compactly as a series of bits
(0 or 1). Naturally, 0 means false and 1 means true. The array is nothing but multiple bits
lined up together:
110010100
means...
true, true, false, false, true, false, true, false, false
That same series of bits (110010100) can also be viewed as a numeral in binary...the equivalent
of:
1 * 2^8 +
1 * 2^7 +
0 * 2^6 +
0 * 2^5 +
1 * 2^4 +
0 * 2^3 +
1 * 2^2 +
0 * 2^1 +
0 * 2^0
Looking at it this way, we can see that each individual boolean in the array can be viewed as a
power of 2 and that the entire array can be viewed as nothing more than the sum of those powers
of 2.
Now that you know a little about bitflags, you can apply it to DamageType. I already mentioned
that addFlag() takes in an integer that is the bitflag of a damage type tag. These bitflags are
just powers of 2 or sums of powers of 2. The easiest way to define these bitflags is through
constant global integers. For example:
globals
constant integer TYPE_PHYSICAL = 0x00001 // 1
constant integer TYPE_ARCANE = 0x00002 // 2
constant integer TYPE_MELEE = 0x00004 // 4
constant integer TYPE_RANGED = 0x00008 // 8
constant integer TYPE_SLASHING = 0x00010 // 16
constant integer TYPE_PIERCE = 0x00020 // 32
constant integer TYPE_BLUNT = 0x00040 // 64
constant integer TYPE_SIEGE = 0x00080 // 128
constant integer TYPE_LIGHT = 0x00100 + TYPE_ARCANE // 256 + 2
constant integer TYPE_DARK = 0x00200 + TYPE_ARCANE // 512 + 2
constant integer TYPE_BLOCKABLE = 0x00400 // 1024
constant integer TYPE_EVADEABLE = 0x00800 // 2048
endglobals
I used hexadecimal notation simply because it looks cleaner; you can use either decimal or hex
notation.
Once you have defined your damage type tags, you must update the static field
FLAG_MAX_EXPONENT to reflect the greatest power of 2 less than or equal to the greatest-valued
flag you have defined. In the above example, FLAG_MAX_EXPONENT would have to be set equal to
11 because the greatest-valued tag (TYPE_EVADEABLE) has a value of 2048, which is equal to
2^11.
You may define as many flags as you want, but the highest value any flag may have is 2^31 - 1.
This is because JASS integers are 32-bit signed and will not hold higher values than 2^31 - 1.
Note that TYPE_LIGHT and TYPE_DARK consist of a power of 2 added to another flag. Adding
together flags basically means combining properties, so now both Light and Dark are considered
to also be Arcane. Adding together equal flags should not be done; doing so will cause the
system to behave incorrectly. Therefore the following should not be allowed:
TYPE_BLUNT + TYPE_BLUNT
TYPE_LIGHT + TYPE_ARCANE // a bit harder to see why, but since TYPE_LIGHT already contains the
// TYPE_ARCANE bitflag (remember it's defined as 0x00100 + TYPE_ARCANE)
// this is essentially doing 0x00100 + TYPE_ARCANE + TYPE_ARCANE.
Because adding together flags accomplishes the same thing as adding them separating to an
instance of DamageType, the following two pieces of code will do the same thing:
------
damageType.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_BLUNT)
------
damageType.addFlag(TYPE_PHYSICAL)
damageType.addFlag(TYPE_MELEE)
damageType.addFlag(TYPE_BLUNT)
------
In both cases, damageType will be considered to be Physical, Melee, and Blunt.
Checking to see if an instance of DamageType has certain damage properties is done through the
hasFlag() method. So for the DamageType instance used above as an example:
damageType.hasFlag(TYPE_PHYSICAL) // returns true
damageType.hasFlag(TYPE_BLUNT) // returns true
damageType.hasFlag(TYPE_ARCANE) // returns false
damageType.hasFlag(TYPE_PHYSICAL + TYPE_MELEE) // returns true
damageType.hasFlag(TYPE_PHYSICAL + TYPE_SLASHING) // returns false
Finally, you can remove damage property flags by using the removeFlag() method. Note that if a
bitflag is entered that encompasses multiple damage properties, removal will only occur if the
DamageType instance has ALL the properties. Once again using our example:
damageType.removeFlag(TYPE_PHYSICAL) // removes physical tag
damageType.removeFlag(TYPE_MELEE) // removes melee tag
damageType.removeFlag(TYPE_BLUNT + TYPE_ARCANE) // doesn't remove either tag
In case you ever need to get the actual bitfield of a DamageType, simply access the readonly
field DamageType.bitfield.
Both DamageEntity and Damager use DamageType by default. Every instance of DamageEntity and
Damager contains the method getDamageType() that returns an instance of DamageType whenever
damage is dealt by that DamageEntity or Damager. That instance is meant to represent the type
of damage dealt by that particular damage entity or damager, and will be passed to the event
response methods that are called through the parameter eventDamageType. For more information on
this, refer to the Damage Engine and Damager documentations.
For examples of how DamageType is used, refer to the triggers under the "Examples" folder.
//TESH.scrollpos=286
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================= DamageEngine API ========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
====================================== struct DamageEngine ======================================
The DamageEngine struct is the core of Damage Engine. It controls the majority of damage
detection and manipulation operations.
Constructors
============
None
Constant Fields
===============
------
static constant integer DETECTION_SPELL_ABIL_ID
------
The raw code of the Damage Detection ability in your map.
------
static constant integer DETECTION_SPELL_BUFF_ID
------
The raw code of the Damage Detection buff in your map.
------
static constant integer MOD_SPELL_ABIL_ID
------
The raw code of the Damage Modification ability in your map.
Static Fields
=============
None
Instance Fields
===============
None
Static Methods
==============
------
static method enable takes boolean b returns nothing
------
Enables or disables the damage detection portion of Damage Engine. Disabling damage detection
means that none of the event response methods will be called.
Parameters:
-boolean b - true to enable damage detection; false to disable damage detection.
See also:
-DamageEngine.isEnabled()
------
static method getDamageEntity takes unit u returns DamageEntity
------
Returns the DamageEntity instance associated with the given unit.
Parameters:
-unit u - the unit with which the desired DamageEntity instance is associated.
Returns:
The DamageEntity instance associated with the given unit.
------
static method getDamageFactor takes unit attacker, unit target, attacktype aType,
damagetype dType returns real
------
Returns the factor applied to triggered damage to determine actual life lost when a unit
damages another using a specific combination of attack and damage types. In other words,
returns the value r such that when the following function is called:
UnitDamageTarget(attacker, target, damage, false, false, aType, dType, wType)
where damage > 1.00 and wType is any weapontype, r = [actual life lost by target] / damage.
This method does not take into consideration any damage modification that may be done by Damage
Engine.
Parameters:
-unit attacker - the unit to deal the damage.
-unit target - the unit to suffer the damage.
-attacktype aType - the attacktype of the damage.
-damagetype dType - the damagetype of the damage.
Returns:
The factor applied to triggered damage to determine actual life lost when a unit damages
another.
------
static method getKiller takes nothing returns unit
------
Returns the killing unit in any trigger response to the events:
-EVENT_UNIT_DEATH
-EVENT_PLAYER_UNIT_DEATH
Returns:
The killing unit on a unit death event.
------
static method isEnabled takes nothing returns boolean
------
Returns whether or not the damage detection portion of Damage Engine is enabled.
Returns:
true if damage detection is enabled.
See also:
-DamageEngine.enable()
------
private static method onUnitDamaged takes EventDamage eventDamage returns nothing
------
<DEPRECATED>
(Configurable)
(Event Response)
The general event response method for modification of damage on damage events. This method is
called whenever damage is dealt to any unit. It is called after DamageEntity.onDamage() and
DamageEntity.onDamaged() and all DamageListeners.
Note that if the system had previously modified the damage value of the event damage to 0 before
this methods turn to be called, this method will not be called.
This method is deprecated of v0.9.6; use DamageListener to respond to universal damage events.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onDamage()
-DamageEntity.onDamaged()
------
private static method onUnitEntersMap takes unit u returns DamageEntity
------
(Configurable)
This method is called whenever a unit enters the map. This method is contractually obligated to
always return an instance of DamageEntity. The instance of DamageEntity returned will
automatically be registered as the instance associated with the unit.
Parameters:
-unit u - the unit that entered the map.
Returns:
The instance of DamageEntity to be associated with unit u.
------
private static method onUnitFinalDamaged takes EventDamage eventDamage returns nothing
------
<DEPRECATED>
(Configurable)
(Event Response)
The general event response method for final damage events. This method is called immediately
after a unit is actually dealt non-zero damage. Is is called after DamageEntity.onFinalDamage()
and DamageEntity.onFinalDamaged() and all DamageListeners.Modifying the event damage in this
method has no effect.
This method is deprecated of v0.9.6; use DamageListener to respond to universal damage events.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onFinalDamage()
-DamageEntity.onFinalDamaged()
------
private static method onUnitUpgrade takes unit u returns DamageEntity
------
(Configurable)
This method is called whenever a unit finishes an upgrade. This method is contractually
obligated to always return an instance of DamageEntity. The instance of DamageEntity returned
will automatically be registered as the instance associated with the unit.
Parameters:
-unit u - the unit that finished the upgrade.
Returns:
The instance of DamageEntity to be associated with unit u.
------
static method registerDamageEntity takes DamageEntity damageEntity returns nothing
------
Registers a DamageEntity with Damage Engine. Once a DamageEntity is registered, it will be
associated with the unit passed to the unit parameter of its constructor. Only one DamageEntity
may be registered per unit, so if another DamageEntity is already associated with this
DamageEntitys unit, calling this method unregisters and destroys the other DamageEntity.
Note that explicitly calling this method is not required in the methods
DamageEngine.onUnitEntersMap() and DamageEngine.onUnitUpgrade(); the system automatically
registers the DamageEntity instances returned by those methods.
Parameters:
-DamageEntity damageEntity - the DamageEntity to be registered.
------
static method registerDamageListener takes DamageListener damageListener returns boolean
------
Registers a DamageListener with Damage Engine. A given DamageListener may only be registered
once with the system.
Parameters:
-DamageListener damageListener - the DamageListener to be registered.
Returns:
true if the DamageListener was successfully registered; false otherwise.
------
static method removeDamageEntity takes DamageEntity damageEntity returns nothing
------
Unregisters a DamageEntity with Damage Engine. If a DamageEntity is removed and its unit still
exists in the game, the system will automatically create a default DamageEntity instance and
register it to that unit.
Parameters:
-DamageEntity damageEntity - the DamageEntity to be removed.
------
static method removeDamageListener takes DamageListener damageListener returns boolean
------
Unregisters a DamageListener with Damage Engine.
Parameters:
-DamageListener damageListener - the DamageListener to be removed.
Returns:
true if the DamageListener was previously registered and was successfully removed; false
otherwise.
------
static method removeUnit takes unit u returns nothing
------
<DEPRECATED>
Removes the given unit from the game. Also cleans up any resources tied to the unit used by
Damage Engine.
Parameters:
-unit u - the unit to remove from the the game.
------
static method unitDamageTarget takes unit attacker, unit target, real damage, Damager damager
returns real
------
Causes the given attacker to damage the given target for a certain amount of damage. The given
Damager instance is used to determine the other specifics of the damage, such as the DamageType
associated with the damage, the event damage data, the attacktype, damagetype, weapontype, etc.
Returns the actual amount of damage dealt.
Parameters:
-unit attacker - the unit to deal the damage.
-unit target - the unit to receive the damage.
-real damage - the amount of damage to deal.
-Damager damager - the Damager to use to deal the damage.
Returns:
The actual amount of damage dealt.
See also:
-DamageEntity.damageTarget()
-Damager
------
static method unitDamageTargetForced takes unit attacker, unit target, real damage,
Damager damager returns nothing
------
Causes the given attacker to damage the given target for a certain amount of damage. The given
Damager instance is used to determine the other specifics of the damage, such as the DamageType
associated with the damage, the event damage data, etc.
This method ignores the return value of Damager.isTarget() and the Damager's Damager.aType and
Damager.dType fields. It will also not trigger any of the the non-final damage event-response
methods of any DamageEntity or DamageListener, but will trigger final damage event-response
methods (if Damage Engine is enabled).
Parameters:
-unit attacker - the unit to deal the damage.
-unit target - the unit to receive the damage.
-real damage - the amount of damage to deal.
-Damager damager - the Damager to use to deal the damage.
See also:
-DamageEntity.damageTargetForced()
-Damager
Instance Methods
================
None
//TESH.scrollpos=84
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================= DamageEntity API ========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
====================================== struct DamageEntity ======================================
An instance of DamageEntity represents a unit that is capable of dealing damage and being
damaged in-game, and every unit in the game has an associated DamageEntity instance.
To further customize DamageEntity you should create your own structs that extend DamageEntity
and override the provided stub methods in DamageEntity and/or create new members of your own.
Constructors
============
------
static method create takes unit u returns DamageEntity
------
Creates a new instance of DamageEntity associated with the given unit. Simply instantiating a
new DamageEntity is not enough if you want it to be used by Damage Engine. To do that, you have
to call DamageEntity.register().
Parameters:
-unit u - the unit with which to associate the new DamageEntity
See Also:
-DamageEngine.registerDamageEntity()
Constant Fields
===============
None
Static Fields
=============
None
Instance Fields
===============
------
readonly unit u
------
Always references the unit that this DamageEntity is associated with.
Static Methods
==============
None
Instance Methods
================
------
method damageTarget takes DamageEntity target, real damage, Damager damager returns real
------
Causes the unit with which this DamageEntity is associated to damage the unit with which the
given target DamageEntity is associated for a certain amount of damage. The given Damager
instance is used to determine the other specifics of the damage, such as the DamageType
associated with the damage, the event damage data, the attacktype, damagetype, weapontype, etc.
Returns the actual amount of damage dealt.
Parameters:
-DamageEntity target - the DamageEntity associated with the unit to receive the damage.
-real damage - the amount of damage to deal.
-Damager damager - the Damager to use to deal the damage.
Returns:
The actual amount of damage dealt.
See also:
-DamageEngine.unitDamageTarget()
-Damager
------
method damageTargetForced takes DamageEntity target, real damage, Damager damager returns nothing
------
Causes the unit with which this DamageEntity is associated to damage the unit with which the
given target DamageEntity is associated for a certain amount of damage. The given Damager
instance is used to determine the other specifics of the damage, such as the DamageType
associated with the damage, the event damage data, etc. This method guarantees that the exact
amount of damage specified will be dealt.
This method ignores the return value of Damager.isTarget() and the Damager's Damager.aType and
Damager.dType fields. It will also not trigger any of the the non-final damage event-response
methods of any DamageEntity or DamageListener, but will trigger final damage event-response
methods (if Damage Engine is enabled).
Parameters:
-DamageEntity target - the DamageEntity associated with the unit to receive the damage.
-real damage - the amount of damage to deal.
-Damager damager - the Damager to use to deal the damage.
See also:
-DamageEngine.unitDamageTargetForced()
-Damager
------
stub method getDamageType takes DamageEntity target, boolean spell returns DamageType
------
Returns the DamageType of the event damage when the unit with which this DamageEntity is
associated deals any damage not triggered by Damager, DamageEntity.unitDamageTarget(), or
DamageEntity.damageTarget(). This method is called when such damage is detected immediately
before any of the event response methods.
Parameters:
-DamageEntity target - the DamageEntity associated with the damaged unit.
-boolean spell - true if the event damage was dealt by a spell or was triggered; false if it was
dealt by an attack.
Returns:
The DamageType of the event damage. (By default this method returns a generic DamageType with no
tags added.)
------
stub method onDamage takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this DamageEntity for damage modification of damage dealt by this
DamageEntitys unit. This method is called when the unit with which this DamageEntity is
associated deals damage. It is called before DamageEntity.onDamaged() is called on the target
and before any DamageListeners.
Note that if the system had previously modified the damage value of the event damage to 0 before
this method's turn to be called, this method will not be called.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onDamaged()
-DamageListener.onDamage()
------
stub method onDamaged takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this DamageEntity for damage modification of damage suffered by
this DamageEntitys unit. This method is called when the unit with which this DamageEntity is
associated suffers damage. It is called after DamageEntity.onDamage() is called on the attacker
and before any DamageListeners.
Note that if the system had previously modified the damage value of the event damage to 0 before
this method's turn to be called, this method will not be called.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onDamage()
-DamageListener.onDamage()
------
stub method onFinalDamage takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this DamageEntity for final damage events. This method is called
immediately after the unit with which this DamageEntity is associated actually deals non-zero
damage. It is called before DamageEntity.onFinalDamage() is called on the target and before
any DamageListeners. Modifying the event damage in this method has no effect.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onFinalDamaged()
-DamageListener.onFinalDamage()
------
stub method onFinalDamaged takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this DamageEntity for final damaged events. This method is called
immediately after the unit with which this DamageEntity is associated actually suffers non-zero
damage. It is called after DamageEntity.onFinalDamage() is called on the attacker and before
the any DamageListeners. Modifying the event damage in this method has no effect.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onFinalDamage()
-DamageListener.onFinalDamage()
//TESH.scrollpos=0
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
====================================== DamageListener API =======================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
===================================== struct DamageListener =====================================
DamageListener is a damage event listener that can respond to global damage events. The order
in which DamageListeners are executed is determined by their priorities.
To further customize DamageListener you should create your own structs that extend
DamageListener and override the provided stub methods and/or create new members of your own.
Constructors
============
------
static method create takes integer priority returns DamageListener
------
Creates a new instance of DamageListener with a given priority. Listeners with lower priority
values are executed first in response to damage events. Simply instantiating a listener
is not enough for it to respond to damage events; listeners must be registered with DamageEngine
before they can be executed on damage events.
Parameters:
-integer priority - the priority of the new DamageListener. Listeners with lower priority values
are called first in response to damage events.
See Also:
-DamageEngine.registerDamageListener()
Constant Fields
===============
None
Static Fields
=============
None
Instance Fields
===============
------
readonly integer priority
------
The priority of this DamageListener. Listeners with lower priority values are called first in
response to damage events. For example, given two DamageListeners a and b, if a.priority <
b.priority, a will be executed before b.
Static Methods
==============
None
Instance Methods
================
------
stub method onDamage takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this DamageListener for damage modification. This method is called
when any unit deals damage. It is called after all of the event-response methods in
DamageEntity.
Note that if the system had previously modified the damage value of the event damage to 0 before
this method's turn to be called, this method will not be called.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onDamage()
-DamageEntity.onDamaged()
------
stub method onFinalDamage takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this DamageListener for final damage events. This method is called
immediately after any unit actually deals non-zero damage. It is called after all the event-
response methods in DamageEntity. Modifying the event damage in this method has no effect.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onFinalDamage()
-DamageEntity.onFinalDamaged()
//TESH.scrollpos=101
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
========================================== Damager API ==========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
======================================== struct Damager =========================================
Damager is the struct responsible for controlling triggered damage in Damage Engine. Damager is
able to configure damage, offering options to control the attacktype, damagetype, and weapontype
of damage, used by the wc3 engine, as well as valid targets for damage and the DamageType of
damage being dealt. Damager also offers its own event response methods that are only called when
Damager deals damage, providing opportunities to manipulate and respond to damage events tied to
a specific instance of Damager.
To customize Damager, you should create your own structs that extend Damager and override the
provided stub methods in Damager and/or create new members of your own.
Constructors
============
------
static method create takes nothing returns Damager
------
Creates a new instance of Damager. The following values are set by default:
-Damager.aType = ATTACK_TYPE_NORMAL
-Damager.dType = DAMAGE_TYPE_UNIVERSAL
-Damager.wType = WEAPON_TYPE_WHOKNOWS
-Damager.data = 0
Constant Fields
===============
------
static constant real MAX_COLLISION_SIZE
------
The maximum collision size of any unit in the map.
Static Fields
=============
None
Instance Fields
===============
------
attacktype aType
------
The attacktype to use when this Damager deals damage. The default value is ATTACK_TYPE_NORMAL.
------
damagetype dType
------
The damagetype to use when this Damager deals damage. The default value is
DAMAGE_TYPE_UNIVERSAL.
------
weapontype wType
------
The weapontype to use when this Damager deals damage. The default value is WEAPON_TYPE_WHOKNOWS.
------
integer data
------
The data to pass to any event damage triggered by this Damager.
See Also:
-EventDamage.data
Static Methods
==============
None
Instance Methods
================
------
method damageAOE takes unit attacker, real x, real y, real radius, real damage returns nothing
------
Causes this Damager to damage all valid targets within a certain radius of the given
coordinates, crediting the damage to a certain unit.
Parameters:
-unit attacker - the unit to which to credit the damage.
-real x - the x coordinate of the center of the area of effect.
-real y - the y coordinate of the center of the area of effect.
-real radius - the radius of the area of effect.
-real damage - the amount of damage to deal.
------
method damageAOEForced takes unit attacker, real x, real y, real radius, real damage returns
nothing
------
Causes this Damager to damage all targets within a certain radius of the given coordinates,
crediting the damage to a certain unit, and guaranteeing that the exact amount of damage
specified will be dealt.
This method ignores the return value of Damager.isTarget() and this Damager's Damager.aType and
Damager.dType fields. It will also not trigger any of the the non-final damage event-response
methods of any DamageEntity or DamageListener, but will trigger final damage event-response
methods (if Damage Engine is enabled).
Parameters:
-unit attacker - the unit to which to credit the damage.
-real x - the x coordinate of the center of the area of effect.
-real y - the y coordinate of the center of the area of effect.
-real radius - the radius of the area of effect.
-real damage - the amount of damage to deal.
------
method damageGroup takes unit attacker, group targets, real damage returns nothing
------
Causes this Damager to damage all valid targets within the given unit group, crediting the
damage to a certain unit.
Parameters:
-unit attacker - the unit to which to credit the damage.
-group targets - the unit group to damage.
-real damage - the amount of damage to deal.
------
method damageGroupForced takes unit attacker, group targets, real damage returns nothing
------
Causes this Damager to damage all targets within the given unit group, crediting the damage to a
certain unit, and guaranteeing that the exact amount of damage specified will be dealt.
This method ignores the return value of Damager.isTarget() and this Damager's Damager.aType and
Damager.dType fields. It will also not trigger any of the the non-final damage event-response
methods of any DamageEntity or DamageListener, but will trigger final damage event-response
methods (if Damage Engine is enabled).
Parameters:
-unit attacker - the unit to which to credit the damage.
-group targets - the unit group to damage.
-real damage - the amount of damage to deal.
------
method damageTarget takes unit attacker, unit target, real damage returns real
------
Causes this Damager to damage a target unit, crediting the damage to a certain unit. Returns
the actual damage dealt to the target.
Parameters:
-unit attacker - the unit to which to credit the damage.
-unit target - the unit to damage.
-real damage - the amount of damage to deal.
Returns:
The amount of damage actually dealt to the target.
------
method damageTargetForced takes unit attacker, unit target, real damage returns nothing
------
Causes this Damager to damage a target unit, crediting the damage to a certain unit, and
guaranteeing that the exact amount of damage specified will be dealt.
This method ignores the return value of Damager.isTarget() and this Damager's Damager.aType and
Damager.dType fields. It will also not trigger any of the the non-final damage event-response
methods of any DamageEntity or DamageListener, but will trigger final damage event-response
methods (if Damage Engine is enabled).
Parameters:
-unit attacker - the unit to which to credit the damage.
-unit target - the unit to damage.
-real damage - the amount of damage to deal.
------
stub method getDamageType takes unit attacker, unit target returns DamageType
------
Returns the DamageType of the event damage when this Damager deals damage. This method is called
when such damage is detected immediately before any of the event response methods.
Parameters:
-unit attacker - the unit to which the damage is credited.
-unit target - the unit suffering the damage.
Returns:
The DamageType of the event damage. (By default this method returns a generic DamageType with no
tags added.)
------
stub method isTarget takes unit attacker, unit target returns boolean
------
Returns whether a potential target unit is a valid target for damage when the damage is credited
to the given attacker. This method is called whenever this Damager attempts to damage a target.
If false is returned, then the Damager will not damage the target, and no event response methods
will be called. By default, this method always returns true.
Parameters:
-unit attacker - the unit to which the damage will be credited.
-unit target - the unit to suffer the damage.
Returns:
true if the target is a valid target for damage.
------
stub method onDamage takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this Damager for damage modification of damage dealt by this
Damager. This method is called when this Damager deals damage. It is called before any of the
event response methods in DamageEntity and any DamageListeners are called.
Note that if the system had previously modified the damage value of the event damage to 0 before
this methods turn to be called, this method will not be called.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onDamage()
-DamageEntity.onDamaged()
-DamageListener.onDamage()
------
stub method onFinalDamage takes EventDamage eventDamage returns nothing
------
(Event Response)
The event response method of this Damager for final damage events. This method is called
immediately after this Damager actually deals non-zero damage. Is is called after all the
final damage event response methods in DamageEntity and all DamageListeners are called.
Modifying the event damage in this method has no effect.
Parameters:
-EventDamage eventDamage - the event damage.
See also:
-EventDamage
-DamageEntity.onFinalDamage()
-DamageEntity.onFinalDamaged()
-DamageListener.onFinalDamage()
//TESH.scrollpos=0
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================== EventDamage API ========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
====================================== struct EventDamage =======================================
A helper struct used by Damage Engine to represent event damage. You should never instantiate or
destroy EventDamage yourself; the system takes care of that.
Constructors
============
None
Constant Fields
===============
None
Static Fields
=============
None
Instance Fields
===============
------
readonly DamageEntity attacker
------
The DamageEntity of the attacking unit.
------
real damage
------
The current damage value of the event damage. Its value is always greater than or equal to 0.
See Also:
-EventDamage.oldDamage
-EventDamage.originalDamage
------
integer data
------
The data associated with the event damage. This value is by default 0 unless the damage was
triggered using a Damager.
See Also:
-Damager
------
readonly real oldDamage
------
The previous value of the event damage before the last modification to EventDamage.damage. If
EventDamage.damage has not yet been modified, then EventDamage.oldDamage == EventDamage.damage.
See Also:
-EventDamage.damage
-EventDamage.originalDamage
------
readonly real originalDamage
------
The original value of the event damage before any modifications to EventDamage.damage. If
EventDamage.damage has not yet been modified, then EventDamage.originalDamage ==
EventDamage.damage.
See Also:
-EventDamage.damage
-EventDamage.oldDamage
------
boolean spell
------
true if the event damage was caused by a spell or a trigger; false if it was caused by an
attack.
------
readonly DamageEntity target
------
The Damage Entity of the damaged unit.
Static Methods
==============
None
Instance Methods
================
None
Delegates
=========
------
private delegate DamageType
------
You can access all fields and methods contained in DamageType in EventDamage.
See Also:
-DamageType
//TESH.scrollpos=0
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================== DamageType API =========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
======================================= struct DamageType =======================================
DamageType is a simple wrapper for a pseudo-bitfield meant to simulate properties of event
damage.
DamageType requires you to define your own bitflags to represent different damage properties.
Constructors
============
------
static method create takes nothing returns DamageType
------
Creates a new instance of DamageType with no flags added (i.e. DamageType.bitfield == 0).
Constant Fields
===============
------
static constant integer FLAG_MAX_EXPONENT
------
(Configurable)
The largest power of two that is less than or equal to any defined DamageType bitflag.
If this value is greater than 30, the system will simply regard it as 30.
Static Fields
=============
None
Instance Fields
===============
------
readonly integer bitfield
------
The bitfield of this DamageType.
Static Methods
==============
None
Instance Methods
================
------
method addFlag takes integer flag returns nothing
------
Adds a flag to this DamageType.
Parameters:
-integer flag - the bitflag to be added.
------
method hasFlag takes integer flag returns boolean
------
Checks if this DamageType contains the given flag.
Parameters:
-integer flag - the bitflag to check against.
Returns:
true if this DamageType contains the given flag.
------
method removeFlag takes integer flag returns nothing
------
Removes a flag from this DamageType. If this DamageType does not contain the flag (i.e. if
DamageType.hasFlag(flag) == false), then this method does nothing.
Parameters:
-integer flag - the bitflag to remove.
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ Damage Engine +
+ v0.A.2 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
library DamageEngine initializer init requires Table, TimerUtils, DamageType
// After saving your map once, you can comment out the next three lines
////! external ObjectMerger w3a AIob DAMD anam "Damage Detection Ability" aart "ReplaceableTextures\CommandButtons\BTNSteelMelee.blp" amat "" asat "" aspt "" atat "" ata0 "" aher 0 aite 0 alev 1 Idam 1 0.0 Iob5 1 0 abuf 1 DAMB ahdu 1 1.00 adur 1 1.00 atar 1 ""
////! external ObjectMerger w3a AIl2 DAMM anam "Damage Modification Ability" ansf "" aart "ReplaceableTextures\CommandButtons\BTNHealthStone.blp" aher 0 aite 0 Ilif 1 100000
////! external ObjectMerger w3h BNab DAMB fnam "Damage Detection Buff" ftip "" fube "" fart "ReplaceableTextures\CommandButtons\BTNSteelMelee.blp" ftat ""
private keyword calculateDamage
private keyword onUnitDamaged
private keyword onUnitFinalDamaged
private keyword damageListeners
private keyword initialize
private keyword locked
struct DamageEngine
// ========================= Configuration =========================
private static method onUnitEntersMap takes unit u returns DamageEntity
//**** Begin example code...
local integer id = GetUnitTypeId(u)
if (id == 'Hpal') then
return Paladin.create(u)
elseif (id == 'Obla') then
return Blademaster.create(u)
elseif (id == 'hfoo') then
return Footman.create(u)
elseif (id == 'hmpr') then
return Priest.create(u)
elseif (id == 'ogru') then
return Grunt.create(u)
elseif (id == 'nbld') then
return BanditLord.create(u)
elseif (id == 'nrog') then
return Rogue.create(u)
elseif (id == 'nass') then
return Assassin.create(u)
endif
//**** End example code...
return DamageEntity.create(u)
endmethod
private static method onUnitUpgrade takes unit u returns DamageEntity
return .getDamageEntity(u)
endmethod
static method onUnitDamaged takes EventDamage eventDamage returns nothing
//DEPRECATED!
endmethod
static method onUnitFinalDamaged takes EventDamage eventDamage returns nothing
//DEPRECATED!
endmethod
// ================================================================
static constant integer DETECTION_SPELL_ABIL_ID = 'DAMD'
static constant integer DETECTION_SPELL_BUFF_ID = 'DAMB'
static constant integer MOD_SPELL_ABIL_ID = 'DAMM'
static method isEnabled takes nothing returns boolean
return IsTriggerEnabled(.damageTrigger)
endmethod
static method enable takes boolean b returns nothing
if (b) then
call EnableTrigger(.damageTrigger)
else
call DisableTrigger(.damageTrigger)
endif
endmethod
static method getDamageEntity takes unit u returns DamageEntity
return .table[u]
endmethod
static method registerDamageEntity takes DamageEntity damageEntity returns nothing
debug call BJDebugMsg("Damage Engine :: Registering DamageEntity " + I2S(damageEntity) + ".")
if (.table.exists(damageEntity.u) == false) then
call UnitAddAbility(damageEntity.u, .DETECTION_SPELL_ABIL_ID)
call TriggerRegisterUnitEvent(.damageTrigger, damageEntity.u, EVENT_UNIT_DAMAGED)
elseif (.table[damageEntity.u] != damageEntity) then
call DamageEntity(.table[damageEntity.u]).destroy()
endif
set .table[damageEntity.u] = damageEntity
endmethod
static method removeDamageEntity takes DamageEntity damageEntity returns nothing
if (.table[damageEntity.u] == damageEntity and GetUnitTypeId(damageEntity.u) != 0) then
set .table[damageEntity.u] = DamageEntity.create(damageEntity.u)
endif
endmethod
static method registerDamageListener takes DamageListener damageListener returns boolean
return DamageEngine.damageListeners.add(damageListener)
endmethod
static method removeDamageListener takes DamageListener damageListener returns boolean
return DamageEngine.damageListeners.remove(damageListener)
endmethod
static method getKiller takes nothing returns unit
if (.killer == null) then
return GetKillingUnit()
endif
return .killer
endmethod
static method unitDamageTarget takes unit attacker, unit target, real damage, Damager damager returns real
local DamageEntity attackerEntity = .table[attacker]
local DamageEntity targetEntity = .table[target]
if (attackerEntity != 0 and targetEntity != 0) then
return attackerEntity.damageTarget(targetEntity, damage, damager)
endif
return 0.0
endmethod
static method unitDamageTargetForced takes unit attacker, unit target, real damage, Damager damager returns nothing
local DamageEntity attackerEntity = .table[attacker]
local DamageEntity targetEntity = .table[target]
if (attackerEntity != 0 and targetEntity != 0) then
call attackerEntity.damageTargetForced(targetEntity, damage, damager)
endif
endmethod
static method getDamageFactor takes unit attacker, unit target, attacktype aType, damagetype dType returns real
local boolean enabled = .isEnabled()
local real maxLife = GetUnitState(target, UNIT_STATE_MAX_LIFE)
local real life = GetWidgetLife(target)
local real factor
call SetWidgetLife(target, maxLife)
call DamageEngine.enable(false)
call UnitDamageTarget(attacker, target, 1.50, false, false, aType, dType, WEAPON_TYPE_WHOKNOWS)
call DamageEngine.enable(enabled)
set factor = (maxLife - GetWidgetLife(target)) / 1.50
call SetWidgetLife(target, life)
return factor
endmethod
static method removeUnit takes unit u returns nothing
// DEPRECATED!
local DamageEntity damageEntity = .table[u]
if (damageEntity != 0) then
call damageEntity.destroy()
endif
call .table.flush(u)
call RemoveUnit(u)
endmethod
// ================================================================
private static trigger damageTrigger
private static HandleTable table
static DamageListenerSet damageListeners
private static unit killer = null
implement optional HealEngineModule
private static method create takes nothing returns DamageEngine
return DamageEngine.allocate()
endmethod
static method calculateDamage takes EventDamage eventDamage returns nothing
local DamageListenerIterator it
if (eventDamage.damage > 0 and .isEnabled()) then
call eventDamage.attacker.onDamage(eventDamage)
if (eventDamage.damage > 0) then
call eventDamage.target.onDamaged(eventDamage)
endif
if (eventDamage.damage > 0) then
set it = DamageEngine.damageListeners.iterator()
loop
exitwhen (it.hasNext() == false or eventDamage.damage <= 0)
call it.next().onDamage(eventDamage)
endloop
call it.destroy()
endif
if (eventDamage.damage > 0) then
call DamageEngine.onUnitDamaged(eventDamage)
endif
endif
endmethod
private static method modifyDamageCallback takes nothing returns nothing
local timer t = GetExpiredTimer()
local ModifyData data = GetTimerData(t)
local real maxLife = GetUnitState(data.eventDamage.target.u, UNIT_STATE_MAX_LIFE)
local DamageListenerIterator it
local boolean enabled = .isEnabled()
local real factor = .getDamageFactor(data.eventDamage.attacker.u, data.eventDamage.target.u, null, null)
set .killer = data.eventDamage.attacker.u
call .enable(false)
call UnitDamageTarget(data.eventDamage.attacker.u, data.eventDamage.target.u, (GetWidgetLife(data.eventDamage.target.u) - (GetWidgetLife(data.eventDamage.target.u) - data.eventDamage.damage - data.delta) * (maxLife / (maxLife - data.maxDelta))) / factor, true, false, null, null, WEAPON_TYPE_WHOKNOWS)
call .enable(enabled)
//call SetWidgetLife(data.eventDamage.target.u, (GetWidgetLife(data.eventDamage.target.u) - data.eventDamage.damage - data.delta) * (maxLife / (maxLife - data.maxDelta)))
call UnitRemoveAbility(data.eventDamage.target.u, .MOD_SPELL_ABIL_ID)
set .killer = null
if (data.eventDamage.damage > 0) then
set data.eventDamage.locked = true
call data.eventDamage.attacker.onFinalDamage(data.eventDamage)
call data.eventDamage.target.onFinalDamaged(data.eventDamage)
set it = DamageEngine.damageListeners.iterator()
loop
exitwhen (it.hasNext() == false)
call it.next().onFinalDamage(data.eventDamage)
endloop
call it.destroy()
call DamageEngine.onUnitFinalDamaged(data.eventDamage)
endif
call data.eventDamage.destroy()
call data.destroy()
call ReleaseTimer(t)
set t = null
endmethod
private static method modifyDamage takes real oldDamage, EventDamage eventDamage returns nothing
local real life = GetWidgetLife(eventDamage.target.u)
local real delta
local real maxLife = GetUnitState(eventDamage.target.u, UNIT_STATE_MAX_LIFE)
local timer t
local ModifyData data
debug call BJDebugMsg("Damage Engine :: Modifying damage from: " + R2S(oldDamage) + " to: " + R2S(eventDamage.damage) + ".")
call UnitAddAbility(eventDamage.target.u, .MOD_SPELL_ABIL_ID)
call SetWidgetLife(eventDamage.target.u, GetWidgetLife(eventDamage.target.u) + oldDamage)
set delta = GetWidgetLife(eventDamage.target.u) - (life + oldDamage)
set t = NewTimer()
set data = ModifyData.create()
set data.delta = delta
set data.maxDelta = GetUnitState(eventDamage.target.u, UNIT_STATE_MAX_LIFE) - maxLife
set data.eventDamage = eventDamage
set data.maxLife = maxLife
call SetTimerData(t, data)
call TimerStart(t, 0.00, false, function DamageEngine.modifyDamageCallback)
set t = null
endmethod
private static method onDamageEvent takes nothing returns nothing
local DamageEntity attacker = .table[GetEventDamageSource()]
local DamageEntity target = .table[GetTriggerUnit()]
local real damage = GetEventDamage()
local boolean spell
local EventDamage eventDamage
set spell = (attacker != 0 and GetUnitAbilityLevel(target.u, .DETECTION_SPELL_BUFF_ID) == 0)
if (spell == false) then
call UnitRemoveAbility(target.u, .DETECTION_SPELL_BUFF_ID)
endif
if (damage > 0) then
debug call BJDebugMsg("Damage Engine :: Damage Detected: " + GetUnitName(attacker.u) + " damaged " + GetUnitName(target.u) + " for " + R2S(damage) + " damage.")
set eventDamage = EventDamage.create(attacker, target, damage, spell, attacker.getDamageType(target, spell).bitfield, 0)
call DamageEngine.calculateDamage(eventDamage)
call .modifyDamage(damage, eventDamage)
endif
endmethod
private static method onEnter takes nothing returns nothing
call .registerDamageEntity(.onUnitEntersMap(GetTriggerUnit()))
endmethod
private static method onUpgrade takes nothing returns nothing
call .registerDamageEntity(.onUnitUpgrade(GetTriggerUnit()))
endmethod
private static method onDecay takes nothing returns nothing
local unit u = GetTriggerUnit()
local DamageEntity damageEntity = .table[u]
if (damageEntity != 0) then
call damageEntity.destroy()
call .table.flush(u)
else
debug call BJDebugMsg("Damage Engine :: Warning: DamageEntity not found.")
endif
set u = null
endmethod
private static method onRemoveUnit takes unit u returns nothing
local DamageEntity damageEntity = .table[u]
if (damageEntity != 0) then
call damageEntity.destroy()
endif
call .table.flush(u)
endmethod
private static method filter takes nothing returns boolean
call .registerDamageEntity(.onUnitEntersMap(GetFilterUnit()))
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger enterTrigger = CreateTrigger()
local trigger upgradeTrigger = CreateTrigger()
local trigger decayTrigger = CreateTrigger()
local group g = CreateGroup()
set .damageTrigger = CreateTrigger()
set .table = HandleTable.create()
set DamageEngine.damageListeners = DamageListenerBalancedTreeSet.create(0)
call TriggerAddAction(.damageTrigger, function DamageEngine.onDamageEvent)
call TriggerRegisterEnterRectSimple(enterTrigger, bj_mapInitialPlayableArea)
call TriggerAddAction(enterTrigger, function DamageEngine.onEnter)
call TriggerRegisterAnyUnitEventBJ(upgradeTrigger, EVENT_PLAYER_UNIT_UPGRADE_FINISH)
call TriggerAddAction(upgradeTrigger, function DamageEngine.onUpgrade)
call TriggerRegisterAnyUnitEventBJ(decayTrigger, EVENT_PLAYER_UNIT_DECAY)
call TriggerAddAction(decayTrigger, function DamageEngine.onDecay)
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, Condition(function DamageEngine.filter))
call DestroyGroup(g)
set g = null
endmethod
endstruct
struct ModifyData
real delta
real maxDelta
EventDamage eventDamage
real maxLife
endstruct
struct DamageEntity
private static DamageType defaultDamageType
private unit un
implement optional HealEntityModule
static method create takes unit u returns DamageEntity
local DamageEntity this = DamageEntity.allocate()
set this.un = u
return this
endmethod
method operator u takes nothing returns unit
return .un
endmethod
stub method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .defaultDamageType
endmethod
stub method onDamage takes EventDamage eventDamage returns nothing
endmethod
stub method onDamaged takes EventDamage eventDamage returns nothing
endmethod
stub method onFinalDamage takes EventDamage eventDamage returns nothing
endmethod
stub method onFinalDamaged takes EventDamage eventDamage returns nothing
endmethod
method damageTarget takes DamageEntity target, real damage, Damager damager returns real
local real life = GetWidgetLife(target.u)
local EventDamage eventDamage
local boolean enabled = DamageEngine.isEnabled()
local DamageListenerIterator it
local real factor = DamageEngine.getDamageFactor(.u, target.u, damager.aType, damager.dType)
set eventDamage = EventDamage.create(this, target, damage * factor, false, damager.getDamageType(this.u, target.u).bitfield, damager.data)
if (eventDamage.damage > 0) then
if (enabled) then
debug call BJDebugMsg("Damage Engine :: Triggered Damage Detected: " + GetUnitName(.u) + " damaged " + GetUnitName(target.u) + " for " + R2S(eventDamage.damage) + " damage.")
call damager.onDamage(eventDamage)
if (eventDamage.damage > 0) then
call DamageEngine.calculateDamage(eventDamage)
else
set damage = 0
endif
endif
if (eventDamage.damage > 0) then
set factor = DamageEngine.getDamageFactor(.u, target.u, null, null)
call DamageEngine.enable(false)
call UnitDamageTarget(.u, target.u, eventDamage.damage / factor, true, false, null, null, damager.wType)
call DamageEngine.enable(enabled)
set eventDamage.damage = life - GetWidgetLife(target.u)
set damage = eventDamage.damage
if (enabled) then
set eventDamage.locked = true
call .onFinalDamage(eventDamage)
call target.onFinalDamaged(eventDamage)
set it = DamageEngine.damageListeners.iterator()
loop
exitwhen (it.hasNext() == false)
call it.next().onFinalDamage(eventDamage)
endloop
call it.destroy()
call DamageEngine.onUnitFinalDamaged(eventDamage)
call damager.onFinalDamage(eventDamage)
endif
else
set damage = 0
endif
else
set eventDamage.damage = 0
endif
set damage = eventDamage.damage
call eventDamage.destroy()
return damage
endmethod
method damageTargetForced takes DamageEntity target, real damage, Damager damager returns nothing
local real life = GetWidgetLife(target.u)
local EventDamage eventDamage
local boolean enabled = DamageEngine.isEnabled()
local DamageListenerIterator it
local real factor
set eventDamage = EventDamage.create(this, target, damage, false, damager.getDamageType(this.u, target.u).bitfield, damager.data)
if (eventDamage.damage > 0) then
debug call BJDebugMsg("Damage Engine :: Forced Triggered Damage Detected: " + GetUnitName(.u) + " damaged " + GetUnitName(target.u) + " for " + R2S(eventDamage.damage) + " damage.")
set factor = DamageEngine.getDamageFactor(.u, target.u, null, null)
call DamageEngine.enable(false)
call UnitDamageTarget(.u, target.u, eventDamage.damage / factor, true, false, null, null, damager.wType)
call DamageEngine.enable(enabled)
set eventDamage.damage = life - GetWidgetLife(target.u)
if (enabled) then
set eventDamage.locked = true
call .onFinalDamage(eventDamage)
call target.onFinalDamaged(eventDamage)
set it = DamageEngine.damageListeners.iterator()
loop
exitwhen (it.hasNext() == false)
call it.next().onFinalDamage(eventDamage)
endloop
call it.destroy()
call DamageEngine.onUnitFinalDamaged(eventDamage)
call damager.onFinalDamage(eventDamage)
endif
endif
call eventDamage.destroy()
endmethod
method destroy takes nothing returns nothing
call DamageEngine.removeDamageEntity(this)
call this.deallocate()
endmethod
static method initialize takes nothing returns nothing
set .defaultDamageType = DamageType.create()
endmethod
endstruct
struct EventDamage
private real dmg
private real oldDmg
private real origDmg
private boolean sp
integer data
private DamageEntity a
private DamageEntity t
boolean locked
private delegate DamageType dt
static method create takes DamageEntity attacker, DamageEntity target, real damage, boolean spell, integer bf, integer data returns EventDamage
local EventDamage this = EventDamage.allocate()
set this.a = attacker
set this.t = target
set this.dmg = damage
set this.oldDmg = damage
set this.origDmg = damage
set this.sp = spell
set this.data = data
set this.dt = DamageType.create()
set this.locked = false
call this.dt.addFlag(bf)
return this
endmethod
method operator damage takes nothing returns real
return .dmg
endmethod
method operator oldDamage takes nothing returns real
return .oldDmg
endmethod
method operator originalDamage takes nothing returns real
return .origDmg
endmethod
method operator spell takes nothing returns boolean
return .sp
endmethod
method operator attacker takes nothing returns DamageEntity
return .a
endmethod
method operator target takes nothing returns DamageEntity
return .t
endmethod
method operator spell= takes boolean spell returns nothing
if (this.locked) then
return
endif
set this.sp = spell
endmethod
method operator damage= takes real newDamage returns nothing
if (this.locked) then
return
endif
if (newDamage < 0) then
set newDamage = 0
endif
if (newDamage != .dmg) then
set .oldDmg = .dmg
set .dmg = newDamage
endif
endmethod
method destroy takes nothing returns nothing
call .dt.destroy()
call this.deallocate()
endmethod
endstruct
struct DamageListener
private integer pr
static method create takes integer priority returns DamageListener
local DamageListener this = DamageListener.allocate()
set this.pr = priority
return this
endmethod
method operator priority takes nothing returns integer
return .pr
endmethod
method operator < takes DamageListener other returns boolean
if (.priority == other.priority) then
return this < other
endif
return .priority < other.priority
endmethod
stub method onDamage takes EventDamage eventDamage returns nothing
endmethod
stub method onFinalDamage takes EventDamage eventDamage returns nothing
endmethod
endstruct
hook RemoveUnit DamageEngine.onRemoveUnit
private function init takes nothing returns nothing
call DamageEntity.initialize()
endfunction
endlibrary
//! runtextmacro Collection("DamageListener", "DamageListener", "DAMAGE_LISTENER")
//! runtextmacro TreeSet("DamageListener", "DamageListener", "DAMAGE_LISTENER")
//TESH.scrollpos=27
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ Damager +
+ v0.A.2 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
library Damager initializer init requires DamageEngine, TimerUtils
struct Damager
// ========================= Configuration =========================
static constant real MAX_COLLISION_SIZE = 200.0
// =================================================================
private static group g = CreateGroup()
private static boolexpr filterExpr
private static Damager temp
private static unit attacker = null
private static real x = 0
private static real y = 0
private static real radius = 0
private static real damage = 0
private static boolean forced = false
private static DamageType defaultDamageType
attacktype aType = ATTACK_TYPE_NORMAL
damagetype dType = DAMAGE_TYPE_UNIVERSAL
weapontype wType = WEAPON_TYPE_WHOKNOWS
integer data = 0
method damageTarget takes unit attacker, unit target, real damage returns real
if (.isTarget(attacker, target)) then
return DamageEngine.unitDamageTarget(attacker, target, damage, this)
endif
return 0.0
endmethod
method damageAOE takes unit attacker, real x, real y, real radius, real damage returns nothing
set .temp = this
set .attacker = attacker
set .x = x
set .y = y
set .radius = radius
set .damage = damage
set .forced = false
call GroupEnumUnitsInRange(.g, x, y, radius + .MAX_COLLISION_SIZE, .filterExpr)
endmethod
method damageGroup takes unit attacker, group targets, real damage returns nothing
set .temp = this
set .attacker = attacker
set .damage = damage
set .forced = false
call ForGroup(targets, function Damager.enumerate)
endmethod
method damageTargetForced takes unit attacker, unit target, real damage returns nothing
call DamageEngine.unitDamageTargetForced(attacker, target, damage, this)
endmethod
method damageAOEForced takes unit attacker, real x, real y, real radius, real damage returns nothing
set .temp = this
set .attacker = attacker
set .x = x
set .y = y
set .radius = radius
set .damage = damage
set .forced = true
call GroupEnumUnitsInRange(.g, x, y, radius + .MAX_COLLISION_SIZE, .filterExpr)
endmethod
method damageGroupForced takes unit attacker, group targets, real damage returns nothing
set .temp = this
set .attacker = attacker
set .damage = damage
set .forced = true
call ForGroup(targets, function Damager.enumerate)
endmethod
stub method getDamageType takes unit attacker, unit target returns DamageType
return .defaultDamageType
endmethod
stub method isTarget takes unit attacker, unit target returns boolean
return true
endmethod
stub method onDamage takes EventDamage eventDamage returns nothing
endmethod
stub method onFinalDamage takes EventDamage eventDamage returns nothing
endmethod
private static method filter takes nothing returns boolean
local Damager this = .temp
local unit u = GetFilterUnit()
if (IsUnitInRangeXY(u, .x, .y, .radius)) then
if (.forced) then
call this.damageTargetForced(.attacker, u, .damage)
else
call this.damageTarget(.attacker, u, .damage)
endif
endif
set u = null
return false
endmethod
private static method enumerate takes nothing returns nothing
local Damager this = .temp
local unit u = GetEnumUnit()
if (.forced) then
call this.damageTargetForced(.attacker, u, .damage)
else
call this.damageTarget(.attacker, u, .damage)
endif
set u = null
endmethod
static method initialize takes nothing returns nothing
set .filterExpr = Condition(function Damager.filter)
set .defaultDamageType = DamageType.create()
endmethod
endstruct
private function init takes nothing returns nothing
call Damager.initialize()
endfunction
endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ Damage Type +
+ v0.A.2 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
library DamageType requires Bitfield
//**** Begin example code...
globals
constant integer TYPE_PHYSICAL = 0x00001
constant integer TYPE_ARCANE = 0x00002
constant integer TYPE_MELEE = 0x00004
constant integer TYPE_RANGED = 0x00008
constant integer TYPE_SLASHING = 0x00010
constant integer TYPE_PIERCE = 0x00020
constant integer TYPE_BLUNT = 0x00040
constant integer TYPE_SIEGE = 0x00080
constant integer TYPE_LIGHT = 0x00100 + TYPE_ARCANE
constant integer TYPE_DARK = 0x00200 + TYPE_ARCANE
constant integer TYPE_BLOCKABLE = 0x00400
constant integer TYPE_EVADEABLE = 0x00800
endglobals
//**** End example code...
struct DamageType
// ========================= Configuration =========================
static constant integer FLAG_MAX_EXPONENT = 11
// =================================================================
private delegate Bitfield bf
static method create takes nothing returns DamageType
local DamageType this = DamageType.allocate()
set this.bf = Bitfield.create(.FLAG_MAX_EXPONENT)
return this
endmethod
method destroy takes nothing returns nothing
call this.bf.destroy()
call this.deallocate()
endmethod
endstruct
endlibrary
//TESH.scrollpos=58
//TESH.alwaysfold=0
library Bitfield
struct Bitfield
private integer bf = 0
private integer maxExponent
static method create takes integer maxExponent returns Bitfield
local Bitfield this = Bitfield.allocate()
set this.maxExponent = maxExponent
return this
endmethod
method operator bitfield takes nothing returns integer
return .bf
endmethod
method hasFlag takes integer flag returns boolean
local integer bf = .bf
local integer exp
local integer i = IMinBJ(.maxExponent, 30)
loop
exitwhen (i < 0)
set exp = R2I(Pow(2, i))
if (exp <= bf) then
set bf = bf - exp
if (flag - exp >= 0) then
set flag = flag - exp
endif
endif
if (flag == 0) then
return true
elseif (bf == 0) then
return false
endif
set i = i - 1
endloop
return false
endmethod
method addFlag takes integer flag returns nothing
local integer bf = .bf
local integer exp
local integer i = IMinBJ(.maxExponent, 30)
loop
exitwhen (i < 0 or flag == 0)
set exp = R2I(Pow(2, i))
if (exp > bf) then
if (flag - exp >= 0) then
set flag = flag - exp
set .bf = .bf + exp
endif
else
set bf = bf - exp
endif
set i = i - 1
endloop
endmethod
method removeFlag takes integer flag returns nothing
if (.hasFlag(flag)) then
set .bf = .bf - flag
endif
endmethod
method clear takes nothing returns nothing
set .bf = 0
endmethod
endstruct
endlibrary
//TESH.scrollpos=89
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
==================================== Heal Engine Module API =====================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
The Heal Engine module introduces several new methods in DamageEngine and DamageEntity.
====================================== struct DamageEngine ======================================
Additional Static Methods
=========================
------
static method registerHealListener takes HealListener healListener returns boolean
------
Registers a HealListener with Damage Engine. A given HealListener may only be registered
once with the system.
Parameters:
-HealListener healListener - the HealListener to be registered.
Returns:
true if the HealListener was successfully registered; false otherwise.
------
static method removeHealListener takes HealListener healListener returns boolean
------
Unregisters a HealListener with Damage Engine.
Parameters:
-HealListener healListener - the HealListener to be removed.
Returns:
true if the DamageListener was previously registered and was successfully removed; false
otherwise.
------
static method unitHealTarget takes unit caster, unit target, real life, Healer healer
returns real
------
Causes the given caster to heal the given target for a certain amount of life. The given
Healer instance is used to determine the other specifics of the healing, such as the HealType
associated with the heal and the event heal data. Returns the actual amount of life healed.
Parameters:
-unit caster - the unit to which to credit the healing.
-unit target - the unit to receive the healing.
-real life - the amount of life to heal.
-Healer healer - the Healer to use to execute the healing.
Returns:
The actual amount of life healed.
See also:
-DamageEntity.healTarget()
-Healer
------
static method unitHealTargetForced takes unit caster, unit target, real life, Healer healer
returns nothing
------
Causes the given caster to heal the given target for a certain amount of life. The given
Healer instance is used to determine the other specifics of the healing, such as the HealType
associated with the heal and the event heal data. This method guarantees that the amount of
life specified is the actual amount healed (unless the amount would heal the target to above
its maximum life, in which case the target will be healed to its maximum life).
This method ignores the return value of Healer.isTarget(). It will also not trigger any of the
the non-final heal event-response methods of any DamageEntity or HealListener, but will trigger
final heal event-response methods (if Damage Engine is enabled).
Parameters:
-unit caster - the unit to which to credit the healing.
-unit target - the unit to receive the healing.
-real life - the amount of life to heal.
-Healer healer - the Healer to use to execute the healing.
See also:
-DamageEntity.healTargetForced()
-Healer
====================================== struct DamageEntity ======================================
Additional Instance Methods
===========================
------
method healTarget takes DamageEntity target, real life, Healer healer returns nothing
------
Causes the unit with which this DamageEntity is associated to heal the unit with which the
given target DamageEntity is associated for a certain amount of life. The given Healer
instance is used to determine the other specifics of the healing, such as the HealType
associated with the healing and the event heal data. This method guarantees that the amount of
life specified is the actual amount healed (unless the amount would heal the target to above
its maximum life, in which case the target will be healed to its maximum life).
This method ignores the return value of Healer.isTarget(). It will also not trigger any of the
the non-final heal event-response methods of any DamageEntity or HealListener, but will trigger
final heal event-response methods (if Damage Engine is enabled).
Parameters:
-DamageEntity target - the DamageEntity associated with the unit to receive the healing.
-real life - the amount of life to heal.
-Healer healer - the Healer to use to execute the healing.
See also:
-DamageEngine.unitHealTargetForced()
-Healer
------
stub method onFinalHeal takes EventHeal eventHeal returns nothing
------
(Event Response)
The event response method of this DamageEntity for final heal events. This method is called
immediately after this DamageEntity's unit actually heals another using Healer. It is called
before DamageEntity.onFinalHealed() is called on the target of the healing and before any
HealListeners. Modifying the life value of the event heal in this method has no effect.
Parameters:
-EventHeal eventHeal - the event heal.
See also:
-EventHeal
-DamageEntity.onFinalHealed()
-HealListener.onFinalHeal()
------
stub method onFinalHealed takes EventHeal eventHeal returns nothing
------
(Event Response)
The event response method of this DamageEntity for final heal events. This method is called
immediately after this DamageEntity's unit is actually healed using Healer. It is called after
DamageEntity.onFinalHeal() is called on the unit to which the healing is credited and before any
HealListeners. Modifying the life value of the event heal in this method has no effect.
Parameters:
-EventHeal eventHeal - the event heal.
See also:
-EventHeal
-DamageEntity.onFinalHeal()
-HealListener.onFinalHeal()
------
stub method onHeal takes EventHeal eventHeal returns nothing
------
(Event Response)
The event-response method of this DamageEntity for modification of healing triggered by Healer
credited to this DamageEntity's unit. It is called before DamageEntity.onHealed() is called on
the target of the healing and before any HealListeners.
Parameters:
-EventHeal eventHeal - the event heal.
See Also:
-EventHeal
-DamageEntity.onHealed()
-HealListener.onHeal()
------
stub method onHealed takes EventHeal eventHeal returns nothing
------
(Event Response)
The event-response method of this DamageEntity for modification of healing triggered by Healer
targeted at this DamageEntity's unit. It is called after DamageEntity.onHeal() is called on the
unit to which the healing is credited and before any HealListeners.
Note that if the system had previously modified the life value of the event heal to 0 before
this method's turn to be called, this method will not be called.
Parameters:
-EventHeal eventHeal - the event heal.
See Also:
-EventHeal
-DamageEntity.onHeal()
-HealListener.onHeal()
//TESH.scrollpos=93
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
========================================== Healer API ===========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
========================================= struct Healer =========================================
Healer is the struct responsible for controlling triggered healing in Damage Engine. Healer is
able to configure healing, offering options to control valid targets for healing and the
HealType of the healing. Healer also offers its own event response methods that are only called
when Healer triggers healing, providing opportunities to manipulate and respond to heal events
tied to a specific instance of Healer.
To customize Healer, you should create your own structs that extend Healer and override the
provided stub methods and/or create new members of your own.
Constructors
============
------
static method create takes nothing returns Healer
------
Creates a new instance of Healer. The following values are set by default:
-Healer.data = 0
Constant Fields
===============
------
static constant real MAX_COLLISION_SIZE
------
The maximum collision size of any unit in the map.
Static Fields
=============
None
Instance Fields
===============
------
integer data
------
The data to pass to any event heal triggered by this Healer.
See Also:
-EventHeal.data
Static Methods
==============
None
Instance Methods
================
------
stub method getHealType takes unit caster, unit target returns HealType
------
Returns the HealType of the event heal when this HealType deals damage. This method is called
when such healing is detected immediately before any of the event response methods.
Parameters:
-unit caster - the casting (healing) unit.
-unit target - the healed unit.
Returns:
The HealType of the event heal. (By default this method returns a generic HealType with no
tags added.)
------
method healAOE takes unit caster, real x, real y, real radius, real life returns nothing
------
Causes this Heal to heal all valid targets within a certain radius of the given coordinates,
crediting the healing to a certain unit.
Parameters:
-unit caster - the unit to which to credit the healing.
-real x - the x coordinate of the center of the area of effect.
-real y - the y coordinate of the center of the area of effect.
-real radius - the radius of the area of effect.
-real life - the amount of life to heal.
------
method healAOEForced takes unit caster, real x, real y, real radius, real life returns nothing
------
Causes this Heal to heal all targets within a certain radius of the given coordinates,
crediting the healing to a certain unit, and guaranteeing that the exact amount of life
specified will be healed (unless the amount would heal the target to above its maximum life, in
which case the target will be healed to its maximum life).
This method ignores the return value of Healer.isTarget(). It will also not trigger any of the
the non-final heal event-response methods of any DamageEntity or HealListener, but will trigger
final heal event-response methods (if Damage Engine is enabled).
Parameters:
-unit caster - the unit to which to credit the healing.
-real x - the x coordinate of the center of the area of effect.
-real y - the y coordinate of the center of the area of effect.
-real radius - the radius of the area of effect.
-real life - the amount of life to heal.
------
method healGroup takes unit caster, group targets, real life returns nothing
------
Causes this Healer to heal all valid targets within the given unit group, crediting the healing
to a certain unit.
Parameters:
-unit caster - the unit to which to credit the healing.
-group targets - the unit group to heal.
-real life - the amount of life to heal.
------
method healGroupForced takes unit caster, group targets, real life returns nothing
------
Causes this Healer to heal all targets within the given unit group, crediting the healing
to a certain unit, and guaranteeing that the exact amount of life specified will be healed
(unless the amount would heal the target to above its maximum life, in which case the target will
be healed to its maximum life).
This method ignores the return value of Healer.isTarget(). It will also not trigger any of the
the non-final heal event-response methods of any DamageEntity or HealListener, but will trigger
final heal event-response methods (if Damage Engine is enabled).
Parameters:
-unit caster - the unit to which to credit the healing.
-group targets - the unit group to heal.
-real life - the amount of life to heal.
------
method healTarget takes unit caster, unit target, real life returns real
------
Causes this Heal to heal a target unit, crediting the healing to a certain unit. Returns the
actual life healed.
Parameters:
-unit caster - the unit to which to credit the healing.
-unit target - the unit to heal.
-real life - the amount of life to heal.
Returns:
The amount of life actually healed.
------
method healTargetForced takes unit caster, unit target, real life returns nothing
------
Causes this Heal to heal a target unit, crediting the healing to a certain unit, and guaranteeing
that the exact amount of life specified will be healed (unless the amount would heal the target
to above its maximum life, in which case the target will be healed to its maximum life).
This method ignores the return value of Healer.isTarget(). It will also not trigger any of the
the non-final heal event-response methods of any DamageEntity or HealListener, but will trigger
final heal event-response methods (if Damage Engine is enabled).
Parameters:
-unit caster - the unit to which to credit the healing.
-unit target - the unit to heal.
-real life - the amount of life to heal.
------
stub method isTarget takes unit caster, unit target returns boolean
------
Returns whether a potential target unit is a valid target for healing when the healing is
credited to a certain caster. This method is called whenever this Healer attempts to heal a
target. If false is returned, then the Healer will not heal the target, and no event response
methods will be called. By default, this method always returns true.
Parameters:
-unit caster - the unit to which the healing will be credited.
-unit target - the unit to heal.
Returns:
true if the target is a valid target for healing.
------
stub method onHeal takes EventHeal eventHeal returns nothing
------
(Event Response)
The event response method of this Healer for healing modification of healing triggered by this
healer. This method is called when this Healer heals a unit. It is called before any of the
event response methods in DamageEntity and any HealListeners are called.
Note that if the system had previously modified the life value of the event heal to 0 before
this methods turn to be called, this method will not be called.
Parameters:
-EventHeal eventHeal - the event heal.
See also:
-EventHeal
-DamageEntity.onHeal()
-DamageEntity.onHealed()
-HealListener.onHeal()
------
stub method onFinalHeal takes EventHeal eventHeal returns nothing
------
(Event Response)
The event response method of this Heal for final heal events. This method is called immediately
after this Heal actually heals a unit. Is is called after all the final heal event response
methods in DamageEntity and all HealListeners are called. Modifying the event heal in this
method has no effect.
Parameters:
-EventHeal eventHeal - the event heal.
See also:
-EventHeal
-DamageEntity.onFinalHeal()
-DamageEntity.onFinalHealed()
-HealListener.onFinalHeal()
//TESH.scrollpos=84
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================= HealListener API ========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
====================================== struct HealListener ======================================
HealListener is a heal event listener that can respond to global heal events. The order in which
HealListeners are executed is determined by their priorities.
To further customize HealListener you should create your own structs that extend HealListener
and override the provided stub methods and/or create new members of your own.
Constructors
============
------
static method create takes integer priority returns HealListener
------
Creates a new instance of HealListener with a given priority. Listeners with lower priority
values are executed first in response to heal events. Simply instantiating a listener
is not enough for it to respond to heal events; listeners must be registered with DamageEngine
before they can be executed on heal events.
Parameters:
-integer priority - the priority of the new HealListener. Listeners with lower priority values
are called first in response to heal events.
See Also:
-DamageEngine.registerHealListener()
Constant Fields
===============
None
Static Fields
=============
None
Instance Fields
===============
------
readonly integer priority
------
The priority of this HealListener. Listeners with lower priority values are called first in
response to heal events. For example, given two HealListeners a and b, if a.priority <
b.priority, a will be executed before b.
Static Methods
==============
None
Instance Methods
================
------
stub method onFinalHeal takes EventHeal eventHeal returns nothing
------
(Event Response)
The event response method of this HealListener for final heal events. This method is called
immediately after any unit actually heals another. It is called after all the event-
response methods in DamageEntity. Modifying the life value of the event heal in this method has
no effect.
Parameters:
-EventHeal eventHeal - the event heal.
See also:
-EventHeal
-DamageEntity.onFinalHeal()
-DamageEntity.onFinalHealed()
------
stub method onHeal takes EventHeal eventHeal returns nothing
------
(Event Response)
The event response method of this HealListener for healing modification. This method is called
when any unit heals another using Healer. It is called after all of the heal event-response
methods in DamageEntity.
Note that if the system had previously modified the life value of the event heal to 0 before
this method's turn to be called, this method will not be called.
Parameters:
-EventHeal eventHeal - the event heal.
See also:
-EventHeal
-DamageEntity.onHeal()
-DamageEntity.onHealed()
//TESH.scrollpos=24
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
========================================= EventHeal API =========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
======================================= struct EventHeal ========================================
A helper struct used by Damage Engine to represent event heal. You should never instantiate or
destroy EventHeal yourself; the system takes care of that.
Constructors
============
None
Constant Fields
===============
None
Static Fields
=============
None
Instance Fields
===============
------
readonly DamageEntity caster
------
The DamageEntity of the casting (healing) unit.
------
integer data
------
The data associated with the event heal. This value is by default 0 unless the Healer that
triggered this event heal passed data to it.
See Also:
-Healer
------
real life
------
The current life value of the event heal. Its value is always greater than or equal to 0.
See Also:
-EventHeal.oldLife
-EventHeal.originalLife
------
readonly real oldLife
------
The previous life value of the event heal before the last modification to EventHeal.life. If
EventHeal.life has not yet been modified, then EventDamage.oldLife == EventDamage.life.
See Also:
-EventHeal.life
-EventHeal.originalLife
------
readonly real originalDamage
------
The original life value of the event heal before any modifications to EventHeal.life. If
EventHeal.life has not yet been modified, then EventHeal.originalLife == EventHeal.life.
See Also:
-EventHeal.life
-EventHeal.oldLife
------
readonly DamageEntity target
------
The Damage Entity of the target (healed) unit.
Static Methods
==============
None
Instance Methods
================
None
Delegates
=========
------
private delegate HealType
------
You can access all fields and methods contained in HealType in EventHeal.
See Also:
-HealType
//TESH.scrollpos=0
//TESH.alwaysfold=0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
========================================= HealType API ==========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-------------
Version 0.A.1
-------------
======================================== struct HealType ========================================
HealType is a simple wrapper for a pseudo-bitfield meant to simulate properties of event
healing.
HealType requires you to define your own bitflags to represent different healing properties.
Constructors
============
------
static method create takes nothing returns HealType
------
Creates a new instance of HealType with no flags added (i.e. HealType.bitfield == 0).
Constant Fields
===============
------
static constant integer FLAG_MAX_EXPONENT
------
(Configurable)
The largest power of two that is less than or equal to any defined HealType bitflag.
If this value is greater than 30, the system will simply regard it as 30.
Static Fields
=============
None
Instance Fields
===============
------
readonly integer bitfield
------
The bitfield of this HealType.
Static Methods
==============
None
Instance Methods
================
------
method addFlag takes integer flag returns nothing
------
Adds a flag to this HealType.
Parameters:
-integer flag - the bitflag to be added.
------
method hasFlag takes integer flag returns boolean
------
Checks if this HealType contains the given flag.
Parameters:
-integer flag - the bitflag to check against.
Returns:
true if this HealType contains the given flag.
------
method removeFlag takes integer flag returns nothing
------
Removes a flag from this HealType. If this HealType does not contain the flag (i.e. if
HealType.hasFlag(flag) == false), then this method does nothing.
Parameters:
-integer flag - the bitflag to remove.
//TESH.scrollpos=363
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ Heal Engine +
+ v0.A.2 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
library HealEngine initializer init requires HealType
private keyword healListeners
private keyword calculateHeal
private keyword initializeHeal
private keyword locked
struct Healer
// ========================= Configuration =========================
static constant real MAX_COLLISION_SIZE = 200.0
// =================================================================
private static group g = CreateGroup()
private static boolexpr filterExpr
private static Healer temp
private static unit caster = null
private static real x = 0
private static real y = 0
private static real radius = 0
private static real life = 0
private static boolean forced = false
private static HealType defaultHealType
integer data = 0
method healTarget takes unit caster, unit target, real life returns real
if (.isTarget(caster, target)) then
return DamageEngine.unitHealTarget(caster, target, life, this)
endif
return 0.0
endmethod
method healAOE takes unit caster, real x, real y, real radius, real life returns nothing
set .temp = this
set .caster = caster
set .x = x
set .y = y
set .radius = radius
set .life = life
set .forced = false
call GroupEnumUnitsInRange(.g, x, y, radius + .MAX_COLLISION_SIZE, .filterExpr)
endmethod
method healGroup takes unit caster, group targets, real life returns nothing
set .temp = this
set .caster = caster
set .life = life
set .forced = false
call ForGroup(targets, function Healer.enumerate)
endmethod
method healTargetForced takes unit caster, unit target, real life returns nothing
call DamageEngine.unitHealTarget(caster, target, life, this)
endmethod
method healAOEForced takes unit caster, real x, real y, real radius, real life returns nothing
set .temp = this
set .caster = caster
set .x = x
set .y = y
set .radius = radius
set .life = life
set .forced = true
call GroupEnumUnitsInRange(.g, x, y, radius + .MAX_COLLISION_SIZE, .filterExpr)
endmethod
method healGroupForced takes unit caster, group targets, real life returns nothing
set .temp = this
set .caster = caster
set .life = life
set .forced = true
call ForGroup(targets, function Healer.enumerate)
endmethod
stub method getHealType takes unit caster, unit target returns HealType
return .defaultHealType
endmethod
stub method isTarget takes unit caster, unit target returns boolean
return true
endmethod
stub method onHeal takes EventHeal eventHeal returns nothing
endmethod
stub method onFinalHeal takes EventHeal eventHeal returns nothing
endmethod
private static method filter takes nothing returns boolean
local Healer this = .temp
local unit u = GetFilterUnit()
if (IsUnitInRangeXY(u, .x, .y, .radius)) then
if (.forced) then
call this.healTargetForced(.caster, u, .life)
elseif (this.isTarget(.caster, u)) then
call this.healTarget(.caster, u, .life)
endif
endif
set u = null
return false
endmethod
private static method enumerate takes nothing returns nothing
local Healer this = .temp
local unit u = GetEnumUnit()
if (.forced) then
call this.healTargetForced(.caster, u, .life)
elseif (this.isTarget(.caster, u)) then
call this.healTarget(.caster, u, .life)
endif
set u = null
endmethod
static method initialize takes nothing returns nothing
set .filterExpr = Condition(function Healer.filter)
set .defaultHealType = HealType.create()
endmethod
endstruct
module HealEngineModule
static HealListenerSet healListeners
static method registerHealListener takes HealListener listener returns boolean
return DamageEngine.healListeners.add(listener)
endmethod
static method removeHealListener takes HealListener listener returns boolean
return DamageEngine.healListeners.remove(listener)
endmethod
static method unitHealTarget takes unit caster, unit target, real life, Healer healer returns real
local DamageEntity casterEntity = .table[caster]
local DamageEntity targetEntity = .table[target]
if (casterEntity != 0 and targetEntity != 0) then
return casterEntity.healTarget(targetEntity, life, healer)
endif
return 0.0
endmethod
static method unitHealTargetForced takes unit caster, unit target, real life, Healer healer returns nothing
local DamageEntity casterEntity = .table[caster]
local DamageEntity targetEntity = .table[target]
if (casterEntity != 0 and targetEntity != 0) then
call casterEntity.healTargetForced(targetEntity, life, healer)
endif
endmethod
static method calculateHeal takes EventHeal eventHeal returns nothing
local HealListenerIterator it
if (eventHeal.life > 0 and .isEnabled()) then
call eventHeal.caster.onHeal(eventHeal)
if (eventHeal.life > 0) then
call eventHeal.target.onHealed(eventHeal)
endif
if (eventHeal.life > 0) then
set it = DamageEngine.healListeners.iterator()
loop
exitwhen (it.hasNext() == false or eventHeal.life <= 0)
call it.next().onHeal(eventHeal)
endloop
call it.destroy()
endif
endif
endmethod
static method initializeHeal takes nothing returns nothing
set DamageEngine.healListeners = HealListenerBalancedTreeSet.create(0)
endmethod
endmodule
module HealEntityModule
stub method onHeal takes EventHeal eventHeal returns nothing
endmethod
stub method onFinalHeal takes EventHeal eventHeal returns nothing
endmethod
stub method onHealed takes EventHeal eventHeal returns nothing
endmethod
stub method onFinalHealed takes EventHeal eventHeal returns nothing
endmethod
method healTarget takes DamageEntity target, real life, Healer healer returns real
local real currentLife = GetWidgetLife(target.u)
local real maxLife = GetUnitState(target.u, UNIT_STATE_MAX_LIFE)
local EventHeal eventHeal
local boolean enabled = DamageEngine.isEnabled()
local HealListenerIterator it
set eventHeal = EventHeal.create(this, target, life, healer.getHealType(this.u, target.u).bitfield, healer.data)
if (eventHeal.life > 0) then
if (enabled) then
debug call BJDebugMsg("Damage Engine :: Triggered Heal Detected: " + GetUnitName(.u) + " healed " + GetUnitName(target.u) + " for " + R2S(eventHeal.life) + " life.")
call healer.onHeal(eventHeal)
if (eventHeal.life > 0) then
call DamageEngine.calculateHeal(eventHeal)
else
set life = 0
endif
endif
if (eventHeal.life > 0) then
set eventHeal.life = RMinBJ(maxLife - currentLife, eventHeal.life)
set life = eventHeal.life
call SetWidgetLife(target.u, GetWidgetLife(target.u) + eventHeal.life)
if (enabled) then
set eventHeal.locked = true
call .onFinalHeal(eventHeal)
call target.onFinalHeal(eventHeal)
set it = DamageEngine.healListeners.iterator()
loop
exitwhen (it.hasNext() == false)
call it.next().onFinalHeal(eventHeal)
endloop
call it.destroy()
call healer.onFinalHeal(eventHeal)
endif
else
set life = 0
endif
else
set eventHeal.life = 0
endif
set life = eventHeal.life
call eventHeal.destroy()
return life
endmethod
method healTargetForced takes DamageEntity target, real life, Healer healer returns nothing
local real currentLife = GetWidgetLife(target.u)
local real maxLife = GetUnitState(target.u, UNIT_STATE_MAX_LIFE)
local EventHeal eventHeal
local boolean enabled = DamageEngine.isEnabled()
local HealListenerIterator it
set eventHeal = EventHeal.create(this, target, life, healer.getHealType(this.u, target.u).bitfield, healer.data)
if (eventHeal.life > 0) then
debug call BJDebugMsg("Damage Engine :: Triggered Heal Detected: " + GetUnitName(.u) + " healed " + GetUnitName(target.u) + " for " + R2S(eventHeal.life) + " life.")
set eventHeal.life = RMinBJ(maxLife - currentLife, eventHeal.life)
set life = eventHeal.life
call SetWidgetLife(target.u, GetWidgetLife(target.u) + eventHeal.life)
if (enabled) then
set eventHeal.locked = true
call .onFinalHeal(eventHeal)
call target.onFinalHeal(eventHeal)
set it = DamageEngine.healListeners.iterator()
loop
exitwhen (it.hasNext() == false)
call it.next().onFinalHeal(eventHeal)
endloop
call it.destroy()
call healer.onFinalHeal(eventHeal)
endif
endif
call eventHeal.destroy()
endmethod
endmodule
struct EventHeal
private real heal
private real oldHeal
private real origHeal
integer data
private DamageEntity c
private DamageEntity t
boolean locked
private delegate HealType ht
static method create takes DamageEntity caster, DamageEntity target, real life, integer bf, integer data returns EventHeal
local EventHeal this = EventHeal.allocate()
set this.c = caster
set this.t = target
set this.heal = life
set this.oldHeal = life
set this.origHeal = life
set this.data = data
set this.ht = HealType.create()
set this.locked = false
call this.ht.addFlag(bf)
return this
endmethod
method operator life takes nothing returns real
return .heal
endmethod
method operator oldLife takes nothing returns real
return .oldHeal
endmethod
method operator originalLife takes nothing returns real
return .origHeal
endmethod
method operator caster takes nothing returns DamageEntity
return .c
endmethod
method operator target takes nothing returns DamageEntity
return .t
endmethod
method operator life= takes real newLife returns nothing
if (this.locked) then
return
endif
if (newLife < 0) then
set newLife = 0
endif
if (newLife != .heal) then
set .oldHeal = .heal
set .heal = newLife
endif
endmethod
method destroy takes nothing returns nothing
call this.ht.destroy()
call this.deallocate()
endmethod
endstruct
struct HealListener
private integer pr
static method create takes integer priority returns HealListener
local HealListener this = HealListener.allocate()
set this.pr = priority
return this
endmethod
method operator priority takes nothing returns integer
return .pr
endmethod
method operator < takes HealListener other returns boolean
if (.priority == other.priority) then
return this < other
endif
return .priority < other.priority
endmethod
stub method onHeal takes EventHeal eventHeal returns nothing
endmethod
stub method onFinalHeal takes EventHeal eventHeal returns nothing
endmethod
endstruct
private function init takes nothing returns nothing
call Healer.initialize()
call DamageEngine.initializeHeal()
endfunction
endlibrary
//! runtextmacro Collection("HealListener", "HealListener", "HEAL_LISTENER")
//! runtextmacro TreeSet("HealListener", "HealListener", "HEAL_LISTENER")
//TESH.scrollpos=20
//TESH.alwaysfold=0
/*
++++++++++++++++++++++++
+ Heal Type +
+ v0.A.2 +
+ Author: aznricepuff +
++++++++++++++++++++++++
*/
library HealType requires Bitfield
//**** Begin example code...
globals
constant integer HEAL_TYPE_LIGHT = 0x00001
constant integer HEAL_TYPE_DARK = 0x00002
constant integer HEAL_TYPE_PRIEST = 0x00004
endglobals
//**** End example code...
struct HealType
// ========================= Configuration =========================
static constant integer FLAG_MAX_EXPONENT = 2
// =================================================================
private delegate Bitfield bf
static method create takes nothing returns HealType
local HealType this = HealType.allocate()
set this.bf = Bitfield.create(.FLAG_MAX_EXPONENT)
return this
endmethod
method destroy takes nothing returns nothing
call this.bf.destroy()
call this.deallocate()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
v0.A.2
-Now requires jasshelper 0.9.Z.0+
-Now uses TreeSet v0.6.0.
-Fixed bug where Healer.healTargetForced() was behaving exactly like Healer.healTarget() (and thus was not working correctly).
v0.A.1
-Added Damager.damageTargetForced(), Damager.damageAOEForced(), Damager.damageGroupForced(), DamageEngine.unitDamageTargetForced(), and DamageEntity.damageTargetForced().
-Added Healer.healTargetForced(), Healer.healAOEForced(), Healer.healGroupForce(), DamageEngine.unitHealTargetForced(), and DamageEntity.healTargetForced().
-Damager.isTarget() no longer gets called twice on valid targets when calling Damager.damageAOE() or Damager.damageGroup(). This shouldn't have mattered anyway (aside from being slightly inefficient) unless you were using Damager.isTarget() as an event-response method, which you shouldn't be.
v0.A.0
-Added optional HealEngine module.
-Added DamageEngine.getDamageFactor().
-The system now hooks calls to RemoveUnit(), eliminating the need to call DamageEngine.removeUnit(). As such, DamageEngine.removeUnit() is now deprecated.
-Fixed bug where the system would not apply the correct damage when targeting heroes and units with magic resistance.
-Some Demo Map changes.
-Documentation now explicitly states that Damage Engine requires Object Merger.
-Fixed some documentation errors.
v0.9.7
-EventDamage.damage can no longer hold any values less than 0. Any attempt to set the value less than 0 will simply result in the value being set to 0.
-When modifying EventDamage.damage, EventDamage will only update EventDamage.oldDamage when the new value is actually different from the current value.
-EventDamage.damage, when accessed in response to damage triggered by Damager and before the value is modified, now returns the event damage value after wc3 attack/damage type + armor factors are taken into account to be consistent with EventDamage.damage in response to non-triggered damage. (For example, assuming that no gameplay constants are changed, if Damager is used to trigger 100.0 magic damage against a unit with heavy armor, EventDamage.damage will initially return 200.0 instead of 100.0.)
-All fields within EventDamage (except EventDamage.data) can no longer be modified in final damage event responses (there will be no compile errors if this is attempted, but the values simply will not be changed during run-time).
-Deaths caused by normal attacks are now correctly credited to the killing unit (meaning bounty/experience gained now works properly).
-Fixed bug where the system was calling DamageListener.onDamage() instead of DamageListener.onFinalDamage() in response to damage events triggered by Damager.
-Some Demo Map changes.
v0.9.6
-Now requires TreeSet
-Major refactor: moved lots of code to new DamageEngine struct, including (but not limited to):
-DamageEngine.onUnitEntersMap()
-DamageEngine.onUnitUpgrade()
-DamageEngine.enable()
-DamageEngine.isEnabled()
-DamageEngine.getDamageEntity()
-DamageEngine.onUnitDamaged()
-DamageEngine.onUnitFinalDamaged()
-Added new DamageListener struct intended to replace DamageEngine.onUnitDamaged() and DamageEngine.onUnitFinalDamaged().
-Deprecated DamageEngine.onUnitDamaged() and DamageEngine.onUnitFinalDamaged(). Left for (kind-of) backwards compatbility.
-Documentation update for the refactor.
v0.9.5
-Fixed serious regression in v0.9.4 where DamageType.addFlag() would not correctly add flags at all.
-Minor documentation fixes.
v0.9.4
-Added method .damageGroup() to Damager.
-Fixed bug where DamageType.addFlag() would not work correctly when attempting to add flags that had already been added to the same DamageType.
-Fixed documentation errors.
v0.9.3
-Fixed syntax errors while saving in debug mode.
v0.9.2
-Removed unneeded explicit constructor from Damager.
-Fixed some documentation errors.
v0.9.1
-Now uses external ObjectMerger call to create necessary abilities/buff.
-Refactored code to use EventDamage struct.
-Changed method names in DamageType.
-Added readonly field 'bitfield' in DamageType.
-Deleted unwanted BJDebugMsg in example code.
-Fixed some documentation errors.
v0.9.0
-First public release.
//TESH.scrollpos=35
//TESH.alwaysfold=0
library Vector
struct Vector
private real dx
private real dy
private real mag
private real angle
static method create takes real x, real y returns Vector
local Vector this = Vector.allocate()
set this.dx = x
set this.dy = y
set this.mag = SquareRoot(x * x + y * y)
set this.angle = Atan2(y, x)
return this
endmethod
method operator x takes nothing returns real
return .dx
endmethod
method operator y takes nothing returns real
return .dy
endmethod
method operator r takes nothing returns real
return .mag
endmethod
method operator theta takes nothing returns real
return .angle
endmethod
method plus takes Vector other returns Vector
return Vector.create(.dx + other.dx, .dy + other.dy)
endmethod
method minus takes Vector other returns Vector
return Vector.create(.dx - other.dx, .dy - other.dy)
endmethod
method times takes real r returns Vector
return Vector.create(.dx * r, .dy * r)
endmethod
method unitVector takes nothing returns Vector
if (.r == 0) then
return Vector.create(1, 0)
endif
return Vector.create(.dx / .r, .dy / .r)
endmethod
method rescale takes real r returns Vector
local Vector u = .unitVector()
local Vector new = u.times(r)
call u.destroy()
return new
endmethod
method dot takes Vector other returns real
return .dx * other.dx + .dy * other.dy
endmethod
method getAngularDisplacement takes Vector other returns real
return Acos(.dot(other) / (.mag * other.mag))
endmethod
static method vectorFromPolar takes real r, real theta returns Vector
return Vector.create(r * Cos(theta), r * Sin(theta))
endmethod
static method vectorFromLoc takes location loc returns Vector
return Vector.create(GetLocationX(loc), GetLocationY(loc))
endmethod
static method vectorFromUnit takes unit u returns Vector
return Vector.create(GetUnitX(u), GetUnitY(u))
endmethod
static method vectorFromDisplacement takes real x1, real y1, real x2, real y2 returns Vector
local Vector v1 = Vector.create(x1, y1)
local Vector v2 = Vector.create(x2, y2)
local Vector disp = v2.minus(v1)
call v1.destroy()
call v2.destroy()
return disp
endmethod
static method vectorFromDisplacementUnit takes unit u1, unit u2 returns Vector
return Vector.vectorFromDisplacement(GetUnitX(u1), GetUnitY(u1), GetUnitX(u2), GetUnitY(u2))
endmethod
static method getAngleBetweenAngles takes real angle1, real angle2 returns real
local Vector v1 = Vector.vectorFromPolar(1, angle1)
local Vector v2 = Vector.vectorFromPolar(1, angle2)
local real angle = v1.getAngularDisplacement(v2)
call v1.destroy()
call v2.destroy()
return angle
endmethod
endstruct
endlibrary
//TESH.scrollpos=49
//TESH.alwaysfold=0
library Stuff requires TimerUtils
struct Data
integer i = 0
real r = 0
unit u = null
effect fx = null
endstruct
private struct EffectData
effect fx = null
endstruct
function AddLife takes unit u, real amount returns real
local real max = GetUnitState(u, UNIT_STATE_MAX_LIFE)
local real life = GetWidgetLife(u)
set amount = RMinBJ(amount, max - life)
call SetWidgetLife(u, life + amount)
return amount
endfunction
function AddMana takes unit u, real amount returns real
local real max = GetUnitState(u, UNIT_STATE_MAX_MANA)
local real life = GetUnitState(u, UNIT_STATE_MANA)
set amount = RMinBJ(amount, max - life)
call SetUnitState(u, UNIT_STATE_MANA, life + amount)
return amount
endfunction
function CreateTT takes string text, integer r, integer g, integer b, real size, unit u, real z, real fade, real life returns nothing
local texttag tt = CreateTextTagUnitBJ(text, u, z, size, r * 100 / 255.0, g * 100 / 255.0, b * 100 / 255.0, 0)
call SetTextTagPos(tt, GetUnitX(u) - StringLength(text) * 5, GetUnitY(u), z)
call SetTextTagVisibility(tt, true)
call SetTextTagFadepoint(tt, fade)
call SetTextTagLifespan(tt, life)
call SetTextTagVelocity(tt, 0.0, 0.05)
call SetTextTagPermanent(tt, false)
set tt = null
endfunction
private function TimedEffectCallback takes nothing returns nothing
local timer t = GetExpiredTimer()
local EffectData data = GetTimerData(t)
call DestroyEffect(data.fx)
call data.destroy()
call ReleaseTimer(t)
set t = null
endfunction
function AddTimedEffectTarget takes string fx, unit target, string attach, real duration returns effect
local timer t = NewTimer()
local EffectData data = EffectData.create()
set data.fx = AddSpecialEffectTarget(fx, target, attach)
call SetTimerData(t, data)
call TimerStart(t, duration, false, function TimedEffectCallback)
set t = null
return data.fx
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library DamageDisplay initializer init requires Stuff, DamageEngine, HealEngine
globals
public boolean display = false
endglobals
struct DisplayListener extends DamageListener
static constant integer PRIORITY = 10
static method create takes nothing returns DisplayListener
return DisplayListener.allocate(.PRIORITY)
endmethod
method onFinalDamage takes EventDamage eventDamage returns nothing
if (display) then
call CreateTT(I2S(R2I(eventDamage.damage)), 0xff, 0xcc, 0x00, 10, eventDamage.attacker.u, 100.0, 1.0, 2.0)
endif
endmethod
endstruct
struct DisplayHealListener extends HealListener
static constant integer PRIORITY = 10
static method create takes nothing returns DisplayHealListener
return DisplayHealListener.allocate(.PRIORITY)
endmethod
method onFinalHeal takes EventHeal eventHeal returns nothing
if (display) then
call CreateTT(I2S(R2I(eventHeal.life)), 0x00, 0x00, 0xa0, 10, eventHeal.target.u, 100.0, 1.0, 2.0)
endif
endmethod
endstruct
private function actions takes nothing returns nothing
set display = display == false
if (display) then
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10.00, "|cffffcc00Damage Display On.|r")
else
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10.00, "|cffffcc00Damage Display Off.|r")
endif
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-display", true)
call TriggerAddAction(t, function actions)
// We need to register our DamageListener for it to have any effect.
call DamageEngine.registerDamageListener(DisplayListener.create())
// We also need to register our HealListener
call DamageEngine.registerHealListener(DisplayHealListener.create())
endfunction
endlibrary
//TESH.scrollpos=36
//TESH.alwaysfold=0
struct Blademaster extends DamageEntity
static constant real PARRY_BLOCK_CHANCE = 0.25
static constant real CRITICAL_STRIKE_CHANCE = 0.25
static constant real ADRENALINE_RUSH_MANA_FACTOR = 0.25
static constant real EVASION_MISS_CHANCE = 0.25
static constant string ADRENALINE_RUSH_EFFECT = "Abilities\\Spells\\Undead\\ReplenishMana\\SpiritTouchTarget.mdl"
static constant string ADRENALINE_RUSH_EFFECT_ATTACH = "chest"
private static DamageType dt
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
method onDamage takes EventDamage eventDamage returns nothing
// ****Critical Strike****
if (eventDamage.spell == false) then
if (GetRandomReal(0, 1) < .CRITICAL_STRIKE_CHANCE) then
set eventDamage.damage = eventDamage.damage * 2
/*
* Note how to manipulate damage, we just have to work
* with a real. No messing around with SetWidgetLife()
* or UnitDamageTarget(). Also note how we don't have
* to take extra precaution to prevent infinite loops
* because manipulating damage this way can never
* cause an infinite loop.
*/
call CreateTT("Critical!", 0xff, 0x00, 0x00, 10, .u, 150.0, 1.0, 2.0)
endif
endif
endmethod
method onDamaged takes EventDamage eventDamage returns nothing
// ****Evasion****
if (eventDamage.hasFlag(TYPE_EVADEABLE) and GetRandomReal(0, 1) < .EVASION_MISS_CHANCE) then
/*
* Notice how this evasion ability can not only dodge attacks, but also abilities
* AND triggered damage!
*/
call CreateTT("Miss!", 0x00, 0x00, 0xff, 10, eventDamage.attacker.u, 100.0, 1.0, 2.0)
set eventDamage.damage = 0
endif
// ****Parry****
if (eventDamage.damage > 0) then
/*
* We have to check to see if damage is still above 0 because if the Blademaster
* already evaded the damage (see code above), theres no need to block it.
*/
if (eventDamage.hasFlag(TYPE_PHYSICAL + TYPE_BLOCKABLE)) then
// This ability only blocks damage flagged as physical and blockable.
if (GetRandomReal(0, 1) < .PARRY_BLOCK_CHANCE) then
call CreateTT("Block!", 0xff, 0xff, 0x00, 10, .u, 100.0, 1.0, 2.0)
set eventDamage.damage = 0
endif
endif
endif
endmethod
method onFinalDamage takes EventDamage eventDamage returns nothing
// ****Adrenaline Rush****
/*
* We only want the Blademaster to gain mana when it actually deals damage and we want
* the amount of mana it gains to be proportional to the actual damage dealt, so we
* put the code here in onFinalDamage() because by the time this method is called, the
* damage has already been dealt and cannot be manipulated further (at least not by
* Damage Engine).
*/
call DestroyEffect(AddSpecialEffectTarget(.ADRENALINE_RUSH_EFFECT, .u, .ADRENALINE_RUSH_EFFECT_ATTACH))
call AddMana(.u, eventDamage.damage * .ADRENALINE_RUSH_MANA_FACTOR)
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the Blademaster to deal Physical, Melee, Slashing damage
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=9
//TESH.alwaysfold=0
struct Paladin extends DamageEntity
static constant string DIVINE_INTERVENTION_EFFECT = "Abilities\\Spells\\Human\\Resurrect\\ResurrectTarget.mdl"
static constant string DIVINE_INTERVENTION_EFFECT_ATTACH = "origin"
private static DamageType dt
boolean divineIntervention = false
effect divineInterventionBuff = null
boolean fortunesFavor = false
effect fortunesFavorBuff = null
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
method onDamaged takes EventDamage eventDamage returns nothing
// ****Fortune's Favor****
if (.fortunesFavor and GetRandomReal(0, 1) < FortunesFavor_CHANCE) then
call FortunesFavor_healer.healTarget(.u, .u, eventDamage.damage)
set eventDamage.damage = 0
endif
// ****Divine Intervention****
if (.divineIntervention and GetWidgetLife(.u) - eventDamage.damage <= 0.405) then
/*
* Notice that we had to check GetWidgetLife(.u) - damage, not just
* GetWidgetLife(.u) since this event response method is called BEFORE
* the damage is actually dealt.
*/
call SetWidgetLife(.u, GetUnitState(.u, UNIT_STATE_MAX_LIFE) * 0.25)
call DestroyEffect(AddSpecialEffectTarget(.DIVINE_INTERVENTION_EFFECT, .u, .DIVINE_INTERVENTION_EFFECT_ATTACH))
call DestroyEffect(.divineInterventionBuff)
set .divineInterventionBuff = null
set .divineIntervention = false
set eventDamage.damage = 0
endif
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the Paladin to deal Physical, Melee, Blunt damage
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_BLUNT + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=6
//TESH.alwaysfold=0
struct Footman extends DamageEntity
private static DamageType dt
boolean defend
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
method onDamage takes EventDamage eventDamage returns nothing
// ****Defend (damage penalty)****
if (.defend) then
set eventDamage.damage = eventDamage.damage - eventDamage.damage * Defend_DAMAGE_PENALTY
endif
endmethod
method onDamaged takes EventDamage eventDamage returns nothing
// ****Defend (block)****
if (.defend and eventDamage.hasFlag(TYPE_PHYSICAL + TYPE_BLOCKABLE) and GetRandomReal(0, 1) < Defend_BLOCK_CHANCE) then
call CreateTT("Block!", 0xff, 0xff, 0x00, 10, .u, 100.0, 1.0, 2.0)
set eventDamage.damage = 0
endif
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the footman to deal Physical, Melee, Slashing damage
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=0
//TESH.alwaysfold=0
struct Priest extends DamageEntity
private static DamageType dt
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the priest to deal Ranged, Light damage
call .dt.addFlag(TYPE_RANGED + TYPE_LIGHT + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=0
//TESH.alwaysfold=0
struct Grunt extends DamageEntity
private static DamageType dt
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the grunt to deal Physical, Melee, Slashing damage
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=0
//TESH.alwaysfold=0
struct Rogue extends DamageEntity
private static DamageType dt
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
method onFinalDamage takes EventDamage eventDamage returns nothing
// ****Cleave****
if (eventDamage.spell == false and GetRandomReal(0, 1) < Cleave_CHANCE) then
/*
* We dont have to disable damage detection here because there is no risk of
* getting an infinite loop since this code will only run when spell == false and
* damaging units with damagers result in spell == true.
*/
set Cleave_damager.target = eventDamage.target.u
call Cleave_damager.damageAOE(.u, GetUnitX(.u), GetUnitY(.u), Cleave_AOE_RADIUS, eventDamage.damage)
endif
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the rogue to deal Physical, Melee, Slashing damage
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=22
//TESH.alwaysfold=0
struct Assassin extends DamageEntity
private static DamageType dt
private static DamageType backstabDt
method getDamageType takes DamageEntity target, boolean spell returns DamageType
local real facing = GetUnitFacing(.u) * bj_DEGTORAD
local real targetFacing = GetUnitFacing(target.u) * bj_DEGTORAD
// ****Backstab (damage type)****
/*
* Example of how you can dynamically change a unit's damage type. If the target is
* facing more or less away from the Assassin, the Assassin's attack becomes
* unblockable and unevadeable.
*/
if (Vector.getAngleBetweenAngles(facing, targetFacing) < Backstab_FACING_THRESHOLD) then
return .backstabDt
endif
return .dt
endmethod
method onDamage takes EventDamage eventDamage returns nothing
local real facing = GetUnitFacing(.u) * bj_DEGTORAD
local real targetFacing = GetUnitFacing(eventDamage.target.u) * bj_DEGTORAD
// ****Backstab (damage bonus)****
// If the target is facing more or less away from the Assassin, we do bonus damage.
if (Vector.getAngleBetweenAngles(facing, targetFacing) < Backstab_FACING_THRESHOLD) then
set eventDamage.damage = eventDamage.damage + eventDamage.damage * Backstab_DAMAGE_BONUS
endif
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
set .backstabDt = DamageType.create()
// We want the assassin to deal Physical, Ranged, Pierce damage normally...
call .dt.addFlag(TYPE_PHYSICAL + TYPE_RANGED + TYPE_PIERCE + TYPE_BLOCKABLE + TYPE_EVADEABLE)
// ...but on backstab attacks, we want its attacks to be unblockable and unevadeable.
call .backstabDt.addFlag(TYPE_PHYSICAL + TYPE_RANGED + TYPE_PIERCE)
endmethod
endstruct
//TESH.scrollpos=0
//TESH.alwaysfold=0
struct BanditLord extends DamageEntity
private static DamageType dt
method getDamageType takes DamageEntity target, boolean spell returns DamageType
return .dt
endmethod
method onFinalDamaged takes EventDamage eventDamage returns nothing
// ****Counterattack****
if (eventDamage.spell == false and eventDamage.hasFlag(TYPE_PHYSICAL + TYPE_MELEE) and GetRandomReal(0, 1) < Counterattack_CHANCE) then
/*
* We dont have to disable damage detection here because there is no risk of
* getting an infinite loop since this code will only run when spell == false and
* damaging units with damagers result in spell == true.
*
* Note we can also damage targets with damagers using DamageEntity.damageTarget().
* The effect is the same as using Damager.damageTarget(), it's just a matter of
* which one easier to code.
*/
call .damageTarget(eventDamage.target, eventDamage.damage * Counterattack_DAMAGE_FACTOR, Counterattack_damager)
call CreateTT("Counter!", 0xff, 0xff, 0x00, 10, .u, 100.0, 1.0, 2.0)
endif
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
// We want the Bandit Lord to deal Physical, Melee, Slashing damage
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
//TESH.scrollpos=7
//TESH.alwaysfold=0
scope HolySmite initializer init
globals
public constant integer SPELL_ABIL_ID = 'A005'
public constant real DAMAGE = 100.0
private constant string EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
private constant string EFFECT_ATTACH = "origin"
private Damager damager
endglobals
struct HolySmiteDamager extends Damager
private static DamageType dt
static method create takes nothing returns HolySmiteDamager
local HolySmiteDamager this = HolySmiteDamager.allocate()
set this.aType = ATTACK_TYPE_MAGIC
set this.dType = DAMAGE_TYPE_NORMAL
return this
endmethod
method getDamageType takes unit attacker, unit target returns DamageType
return .dt
endmethod
method isTarget takes unit attacker, unit target returns boolean
// We only want this to damage enemies.
return IsUnitEnemy(target, GetOwningPlayer(attacker))
endmethod
method onDamage takes EventDamage eventDamage returns nothing
local real attackerLife = GetUnitStatePercent(eventDamage.attacker.u, UNIT_STATE_LIFE, UNIT_STATE_MAX_LIFE)
local real targetLife = GetUnitStatePercent(eventDamage.target.u, UNIT_STATE_LIFE, UNIT_STATE_MAX_LIFE)
call DestroyEffect(AddSpecialEffectTarget(EFFECT, GetSpellTargetUnit(), EFFECT_ATTACH))
if (targetLife > attackerLife) then
set eventDamage.damage = eventDamage.damage * 2
endif
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
call .dt.addFlag(TYPE_LIGHT)
endmethod
endstruct
private function conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ABIL_ID
endfunction
private function actions takes nothing returns nothing
call damager.damageTarget(GetTriggerUnit(), GetSpellTargetUnit(), DAMAGE)
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
set damager = HolySmiteDamager.create()
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope FaithfulHammer initializer init
globals
public constant integer SPELL_ABIL_ID = 'A007'
public constant real DAMAGE = 125.0
public constant real MANA_LOSS = 100.0
private constant string EFFECT = "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
private constant string EFFECT_ATTACH = "origin"
private Damager damager
endglobals
struct FaithfulHammerDamager extends Damager
private static DamageType dt
method getDamageType takes unit attacker, unit target returns DamageType
return .dt
endmethod
method isTarget takes unit attacker, unit target returns boolean
// We only want this to damage enemies.
return IsUnitEnemy(target, GetOwningPlayer(attacker))
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_BLUNT + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
private function conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ABIL_ID
endfunction
private function actions takes nothing returns nothing
local unit target = GetSpellTargetUnit()
local real damage = damager.damageTarget(GetTriggerUnit(), target, DAMAGE)
if (damage <= 0) then
/*
* We can check to see if the damager was successful at dealing the damage or not.
* This allows us to be able to intelligently execute code based on if the ability
* was evaded, blocked, etc.
*/
call SetUnitState(target, UNIT_STATE_MANA, RMaxBJ(GetUnitState(target, UNIT_STATE_MANA) - MANA_LOSS, 0))
call DestroyEffect(AddSpecialEffectTarget(EFFECT, target, EFFECT_ATTACH))
endif
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
set damager = FaithfulHammerDamager.create()
endfunction
endscope
//TESH.scrollpos=33
//TESH.alwaysfold=0
scope DivineIntervention initializer init
globals
public constant integer SPELL_ABIL_ID = 'A006'
public constant real DURATION = 10.00
private constant string EFFECT = "Abilities\\Spells\\Human\\DivineShield\\DivineShieldTarget.mdl"
private constant string EFFECT_ATTACH = "origin"
endglobals
private function conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ABIL_ID
endfunction
private function callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local Paladin this = GetTimerData(t)
if (this.divineIntervention) then
set this.divineIntervention = false
call DestroyEffect(this.divineInterventionBuff)
set this.divineInterventionBuff = null
endif
call ReleaseTimer(t)
set t = null
endfunction
private function actions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local Paladin this = DamageEngine.getDamageEntity(caster) // This is how we get a unit's
// DamageEntity instance.
local timer t
if (this != 0) then
set this.divineIntervention = true
set this.divineInterventionBuff = AddSpecialEffectTarget(EFFECT, caster, EFFECT_ATTACH)
set t = NewTimer()
call SetTimerData(t, this)
call TimerStart(t, DURATION, false, function callback)
endif
set caster = null
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
endfunction
endscope
//TESH.scrollpos=18
//TESH.alwaysfold=0
scope FortunesFavor initializer init
globals
public constant integer SPELL_ABIL_ID = 'A009'
public constant real DURATION = 30.00
public constant real CHANCE = 0.20
constant string EFFECT = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareTarget.mdl"
constant string EFFECT_ATTACH = "overhead"
public Healer healer
endglobals
private function conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ABIL_ID
endfunction
struct FortunesFavorHealer extends Healer
static constant string EFFECT = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl"
static constant string EFFECT_ATTACH = "origin"
method onFinalHeal takes EventHeal eventHeal returns nothing
call AddTimedEffectTarget(.EFFECT, eventHeal.target.u, .EFFECT_ATTACH, 2.0)
endmethod
endstruct
private function callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local Paladin this = GetTimerData(t)
set this.fortunesFavor = false
call DestroyEffect(this.fortunesFavorBuff)
set this.fortunesFavorBuff = null
call ReleaseTimer(t)
set t = null
endfunction
private function actions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local Paladin this = DamageEngine.getDamageEntity(caster) // This is how we get a unit's
// DamageEntity instance.
local timer t
if (this != 0) then
set this.fortunesFavor = true
set this.fortunesFavorBuff = AddSpecialEffectTarget(EFFECT, caster, EFFECT_ATTACH)
set t = NewTimer()
call SetTimerData(t, this)
call TimerStart(t, DURATION, false, function callback)
endif
set caster = null
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
set healer = FortunesFavorHealer.create()
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Defend initializer init
globals
public constant real BLOCK_CHANCE = 0.50
public constant real DAMAGE_PENALTY = 0.50
endglobals
private function actions takes nothing returns nothing
local string order = OrderId2String(GetIssuedOrderId())
local Footman this = DamageEngine.getDamageEntity(GetTriggerUnit())
if (order == "defend") then
set this.defend = true
elseif (order == "undefend") then
set this.defend = false
endif
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddAction(t, function actions)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Heal initializer init
globals
public constant integer SPELL_ABIL_ID = 'A000'
public constant real LIFE = 15.0
public constant real BONUS_LIFE = 15.0
private Healer healer
endglobals
struct HealHealer extends Healer
private static HealType ht
method getHealType takes unit attacker, unit target returns HealType
return .ht
endmethod
method isTarget takes unit attacker, unit target returns boolean
// We only want this to heal friendlies.
return IsUnitAlly(target, GetOwningPlayer(attacker))
endmethod
method onHeal takes EventHeal eventHeal returns nothing
local real targetLife = GetWidgetLife(eventHeal.target.u) / GetUnitState(eventHeal.target.u, UNIT_STATE_MAX_LIFE)
if (targetLife < 0.50) then
set eventHeal.life = eventHeal.life + BONUS_LIFE
endif
endmethod
private static method onInit takes nothing returns nothing
set .ht = HealType.create()
call .ht.addFlag(TYPE_LIGHT)
endmethod
endstruct
private function conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ABIL_ID
endfunction
private function actions takes nothing returns nothing
call healer.healTarget(GetTriggerUnit(), GetSpellTargetUnit(), LIFE)
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
set healer = HealHealer.create()
endfunction
endscope
//TESH.scrollpos=12
//TESH.alwaysfold=0
scope Sanctuary initializer init
globals
public constant integer SPELL_ABIL_ID = 'A00B'
public constant integer SPELL_BUFF_ID = 'B001'
public constant real DAMAGE_ABSORB = 100.0
private HandleTable table
endglobals
struct SanctuaryData
real r = 0.0
endstruct
/*
* Since Sanctuary can potentially be cast on any type of unit, it makes most
* sense to deal with its effects in a DamageListener, which can respond to
* all damage events unlike a DamageEntity, which only responds to events
* associated with one given unit.
*/
struct SanctuaryListener extends DamageListener
static constant integer PRIORITY = 1
static method create takes nothing returns SanctuaryListener
return SanctuaryListener.allocate(.PRIORITY)
endmethod
private method absorbDamage takes unit target, real damage returns real
local SanctuaryData data = table[target]
local real newDamage
set newDamage = RMaxBJ(damage - data.r, 0)
set data.r = data.r - (damage - newDamage)
if (data.r <= 0) then
call UnitRemoveAbility(target, SPELL_BUFF_ID)
endif
return newDamage
endmethod
method onDamage takes EventDamage eventDamage returns nothing
if (GetUnitAbilityLevel(eventDamage.target.u, SPELL_BUFF_ID) > 0) then
set eventDamage.damage = .absorbDamage(eventDamage.target.u, eventDamage.damage)
endif
endmethod
endstruct
private function conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ABIL_ID
endfunction
private function actions takes nothing returns nothing
local unit target = GetSpellTargetUnit()
local SanctuaryData data
if (table.exists(target)) then
set data = table[target]
else
set data = SanctuaryData.create()
set table[target] = data
endif
set data.r = DAMAGE_ABSORB
set target = null
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
set table = HandleTable.create()
//We need to register our DamageListener for it to have any effect.
call DamageEngine.registerDamageListener(SanctuaryListener.create())
endfunction
endscope
//TESH.scrollpos=15
//TESH.alwaysfold=0
scope Cleave initializer init
globals
public constant real CHANCE = 0.25
public constant real AOE_RADIUS = 125.0
public CleaveDamager damager
endglobals
struct CleaveDamager extends Damager
private static DamageType dt
unit target = null
method getDamageType takes unit attacker, unit target returns DamageType
return .dt
endmethod
method isTarget takes unit attacker, unit target returns boolean
// Only damage non-structure, non-flying enemies
return target != .target and IsUnitEnemy(target, GetOwningPlayer(attacker)) and IsUnitType(target, UNIT_TYPE_FLYING) == false and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
private function init takes nothing returns nothing
set damager = CleaveDamager.create()
endfunction
endscope
//TESH.scrollpos=9
//TESH.alwaysfold=0
scope Counterattack initializer init
globals
public constant real CHANCE = 0.20
public constant real DAMAGE_FACTOR = 0.50
public CounterattackDamager damager
endglobals
struct CounterattackDamager extends Damager
private static DamageType dt
method getDamageType takes unit attacker, unit target returns DamageType
return .dt
endmethod
method isTarget takes unit attacker, unit target returns boolean
// Only damage non-structure, non-flying enemies
return IsUnitEnemy(target, GetOwningPlayer(attacker)) and IsUnitType(target, UNIT_TYPE_FLYING) == false and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false
endmethod
private static method onInit takes nothing returns nothing
set .dt = DamageType.create()
call .dt.addFlag(TYPE_PHYSICAL + TYPE_MELEE + TYPE_SLASHING + TYPE_BLOCKABLE + TYPE_EVADEABLE)
endmethod
endstruct
private function init takes nothing returns nothing
set damager = CounterattackDamager.create()
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Backstab
globals
public constant real FACING_THRESHOLD = bj_PI / 6
public constant real DAMAGE_BONUS = 1.0
endglobals
endscope