Name | Type | is_array | initial_value |
PATROL_Create | trigger | No | |
PATROL_Destroy | trigger | No | |
PATROL_Enabled | boolean | No | |
PATROL_GetData | trigger | No | |
PATROL_GuardDistance | real | No | |
PATROL_Order | string | No | |
PATROL_ORDER_ATTACK | string | No | |
PATROL_ORDER_MOVE | string | No | |
PATROL_PatrolType | integer | No | |
PATROL_Point | location | No | |
PATROL_RegisterPoint | trigger | No | |
PATROL_SetData | trigger | No | |
PATROL_TYPE_BACKWARD | integer | No | |
PATROL_TYPE_LOOP | integer | No | |
PATROL_TYPE_NORMAL | integer | No | |
PATROL_Unit | unit | No | |
TempPoint | location | No | |
TempReal | real | No |
//TESH.scrollpos=615
//TESH.alwaysfold=0
native UnitAlive takes unit u returns boolean
library PatrolPath
//! runtextmacro DEFINE_STRUCT_VECTOR("", "PatrolPath", "PatrolPoint")
endlibrary
//! zinc
library Patrol /* v1.5a --hiveworkshop.com/threads/patrol-waypoint-system.276391/
*/ requires VectorT, /* hiveworkshop.com/threads/containers-vector-t.248942/
*/ UnitDex, /* wc3c.net/showthread.php?t=101322
*/ TimerUtils, /* hiveworkshop.com/threads/system-unitdex-unit-indexer.248209
*/ PatrolPath /*
Information
¯¯¯¯¯¯¯¯¯¯¯
Patrol allows units to walk certain patrol paths with certain behaviour.
PatrolPaths can be simple routes, but a unit can also run complex routes.
With "complex" patrols is meant that you can combine multiple patrol
paths into one bigger, complex patrol path. See demo example.
Applied method
¯¯¯¯¯¯¯¯¯¯¯¯¯¯
1. We create a PatrolPath which is a new vector type from VectorT.
2. We create as many PatrolPoint(s) as we need and add them to a PatrolPath.
(for adding PatrolPoints to a PatrolPath we use the common vector API, see VectorT)
3. We can apply a unit anytime to a certain Path with using our PatrolUnit API.
You are allowed to define a special PatrolType (defines the behaviour) for each patroling unit.
You can also register your code which runs when ever a PatrolPoint is reached.
*/
//! novjass
//=================== --------- API --------- ====================
struct PatrolPoint
real x
real y
static method create(real x, real y)
method destroy()
// With this you can create and destroy PatrolPoints.
// You always can change x/y coordinate of a PatrolPoint at any time.
// PatrolPoint is the type that you need to add to a PatrolPath, example:
local PatrolPath path = PatrolPath.create()
call path.push(PatrolPoint(0,0))
// In the example we create a PatrolPoint(0/0) and add it to our PatrolPath.
// If you wonder where I took the "push" method from, PatrolPath is actually nothing else
// than a vector type from VectorT library. So you can use all API powers from the
// vector library to manage the PatrolPath.
===
struct PatrolType
static constant integer NORMAL // After patrol finished, the unit walks back to start Point.
static constant integer BACKWARD // After patrol finished, the unit walks all the patrol path backwards.
static constant integer LOOP // After patrol finished, the unit will instantly be moved to start.
===
struct PatrolOrder
static constant integer MOVE // The unit will "move" to points.
static constant integer ATTACK // The unit will "attack" to points.
===
struct PatrolUnit
static method create(unit u, PatrolPath pp, PatrolType pt, PatrolOrder po, real guardDistance, boolean reverse) returns thistype
// Applies a patrolpath with a patroltype to a unit
// "reverse" means it would go the PatrolPath in reverse, so start at last position (when set to "true")
// guardDistance is only important if the PatrolOrder is ATTACK.
// it defines the maximum distance before the unit is forced to return patroling.
method destroy()
// Make the unit no PatrolUnit anymore
method operator unit returns unit
// the unit which patrols
method operator pathPosition returns integer
method operator pathPosition= (integer newPos)
// This is the current position of the unit's PatrolPath.
// You ONLY need this actually when you register a code (see later).
// It's advised only to "read" it (see demo), change it only if you know what you're doing.
// minimum index (start) = 0
// maximum index (back) = PatrolPath.size()-1
method operator order returns PatrolOrder
method operator order= (PatrolOrder p)
method operator guardDistance returns real
method operator guardDistance= (real r)
method operator enabled returns boolean
method operator enabled= (boolean flag)
// enables/disable an instance
method operator reverse returns boolean
method operator reverse= (boolean flag)
// "true" means the unit goes the PatrolPath in reverse way.
method operator path returns PatrolPath
method operator path= (PatrolPath p)
// you can apply a new path at any time
// when newly applied, it will start at StartPosition,
// or at respectivly at LastPosition, if "reverse" is "true".
method operator patrolType returns PatrolType
method operator patrolType= (PatrolType p)
// you can change patrol type at any time
static method operator enabledAll= (boolean flag)
// enable or disable all instances
static method operator[] (unit u) returns thistype
// for example:
set PatrolUnit[unit].reverse = true
static method register(boolexpr be) returns triggercondition
static method unregister(triggercondition tc)
// You can register and unregister boolexrepssions with these functions.
// Registered code will run when ever a PatrolUnit reaches a PatrolPoint.
// When the registered code returns "true", the system will normaly
// contitunue the unit's Patrol behaviour.
// When the registered code returns "false", the system thinks that
// you made custom changes and will skip the current PatrolPoint.
method registerPatrolUnit(boolexpr be) returns triggercondition
method unregisterPatrolUnit(triggercondition tc)
// same as above, but specified to your instance
// !! Just as info, first the globaly registered code runs, and then the instance-specified one.
static method operator currentPatrolUnit returns thistype
// use this operator to access the current instance inside registered code.
// e.g: PatrolUnit currentInstance = PatrolUnit.currentPatrolUnit;
===
function PatrolPathHardDestroy(PatrolPath p)
// Of course you always can use the standard destructor .destroy()
// The major difference to this, is that this "Hard Destroy" will also destroy
// all to the PatrolPath binded PatrolPoints. With standard .destroy() you solely
// destroy the PatrolPath itself, but PatrolPoints would still exist.
// The reason for that is, because it is technicaly allowed to use one PatrolPoint
// for multiple PatrolPaths.
// You only should use this function if you truely don't need the binded points anymore.
// Look at demo for examples.
//! endnovjass
{
//=================== --------- Config --------- ====================
private constant boolean DISABLE_ON_DEATH = true; // When a PatrolUnit dies, the instance will be automaticaly disabled
// You will need to enable it by your own again.
private constant boolean DESTROY_ON_DEATH = false; // When a PatrolUnit dies, the instance will be automaticaly destroyed.
private constant boolean FORMATION_ENABLED = false; // Set this to true in case players who control patrol units (can) have the
// formation enabled in their gameplay options.
// If you have formation enabled, you can bug patrol units with it,
// and they would start going crazy when you try to keep some units in format
// that try to reach the same patrol point. So when you set this to "true", the system
// will try to fix this with giving patrol orders to a point with random/minimal offset of
// the wanted x/ym so 2 units will likely never have the problem in being in formated movement
// with also trying to patrol to the very same x/y at the same time.
// but it is just recommended that you leave the formation disabled and set it here to "false".
private constant real MAX_RANGE = 20; // The tolerance distance a unit may have to a patrol point.
//=================== -------------------------- ====================
public struct PatrolPoint{
real x;
real y;
private boolean p_exists; // private_exists
static method create(real x, real y) -> thistype{
thistype this = allocate();
this.x = x;
this.y = y;
this.p_exists = true;
return this;
}
method destroy(){
if (p_exists){
deallocate();
p_exists = false;
}
}
// user doesn't need to know this exists,
// but it makes no harm
method operator exists() -> boolean{
return p_exists;
}
}
public struct PatrolType extends array{
static constant integer NORMAL = 1; // After finished, the unit walks back to start Point.
static constant integer BACKWARD = 2; // After finished, the unit walks all the patrol path backwards.
static constant integer LOOP = 3; // After finished, the unit will instantly be moved to start.
static constant integer MAXIMUM_AMOUNT = 3;
}
public struct PatrolOrder extends array{
static constant integer MOVE = 851986;
static constant integer ATTACK = 851983;
}
private constant real INTERVAL = 0.031250000;
public struct PatrolUnit extends array{
private static group Group = CreateGroup();
private static group Backup = CreateGroup();
private static group Temp;
private static trigger handler = CreateTrigger(); // global handler
private static thistype p_patrolUnit; // access in registered code
// members
private unit p_u; // private_unit
private timer clock;
private integer incrementer; // Defines if current position is going forward or backward in PatrolPath
private integer p_pathPosition; // Current position in PatrolPath vector
private trigger p_handler; // instance specific handler
private boolean p_reverse; // private_reverse
private boolean p_enabled; // private_enabled
private real p_guardDistance;
private trigger guardHandler;
private PatrolType p_patrolType;
private PatrolOrder p_order;
private PatrolPath p_path;
// x/y of the point the unit needs to return, in case it is chasing/fighting some unit
private real xReturn;
private real yReturn;
private boolean isChasing;
method destroy(){
ReleaseTimer(clock);
if(p_handler != null){
DestroyTrigger(p_handler);
p_handler = null;
}
if(guardHandler != null){
DestroyTrigger(guardHandler);
guardHandler = null;
}
p_enabled = false;
GroupRemoveUnit(Group, p_u);
p_u = null;
clock = null;
}
private static method callback(){
thistype this = GetTimerData(GetExpiredTimer());
real distance, x, y;
static if(DESTROY_ON_DEATH){
if (!UnitAlive(p_u)){
destroy();
return;
}
}
static if(DISABLE_ON_DEATH){
if (!UnitAlive(p_u)){
PauseTimer(clock);
p_enabled = false;
return;
}
}
if(p_path.empty()){
debug BJDebugMsg(thistype.callback.name + ": instance " + I2S(this) + " has an invalid or empty PatrolPath.");
return;
}
else if(!p_path[p_pathPosition].exists){
debug BJDebugMsg(thistype.callback.name + ": instance " + I2S(this) + " tries to acces a removed/invalid PatrolPoint.");
return;
}
x = GetUnitX(p_u); y = GetUnitY(p_u);
distance = SquareRoot((p_path[p_pathPosition].x-x)*(p_path[p_pathPosition].x-x) + (p_path[p_pathPosition].y-y)*(p_path[p_pathPosition].y-y));
if (distance <= MAX_RANGE) {
p_patrolUnit = this;
TriggerEvaluate(handler);
if(p_handler != null)
TriggerEvaluate(p_handler);
if (p_patrolType == PatrolType.NORMAL){
if(p_path[p_pathPosition] == p_path.back() && !reverse){
p_pathPosition = 0;
}
else if(reverse && p_path[p_pathPosition] == p_path.front()){
p_pathPosition = p_path.size()-1;
}
else {
p_pathPosition = p_pathPosition + incrementer;
}
}
else if(p_patrolType == PatrolType.BACKWARD){
if(p_path[p_pathPosition] == p_path.back()){
incrementer = -1;
}
else if (p_path[p_pathPosition] == p_path.front()){
incrementer = 1;
}
p_pathPosition = p_pathPosition + incrementer;
}
else if(p_patrolType == PatrolType.LOOP){
if(p_path[p_pathPosition] == p_path.back() && !reverse){
SetUnitX(p_u, p_path.front().x);
SetUnitY(p_u, p_path.front().y);
p_pathPosition = 1;
}
else if(reverse && p_path[p_pathPosition] == p_path.front()){
SetUnitX(p_u, p_path.back().x);
SetUnitY(p_u, p_path.back().y);
p_pathPosition = p_path.size()-2;
}
else {
p_pathPosition = p_pathPosition + incrementer;
}
}
if (FORMATION_ENABLED) {
IssuePointOrderById(p_u, p_order, p_path[p_pathPosition].x + GetRandomReal(0, 0.1), p_path[p_pathPosition].y + GetRandomReal(0, 0.1));
}
else{
IssuePointOrderById(p_u, p_order, p_path[p_pathPosition].x , p_path[p_pathPosition].y);
}
}
// when the unit got off the path for some reason (maybe after a fight with enemies),
// and is being detected without a current order, we re-order it to continue patroling.
if (GetUnitCurrentOrder(p_u) == null){
if (FORMATION_ENABLED) {
IssuePointOrderById(p_u, p_order, p_path[p_pathPosition].x + GetRandomReal(0, 0.1), p_path[p_pathPosition].y + GetRandomReal(0, 0.1));
}
else{
IssuePointOrderById(p_u, p_order, p_path[p_pathPosition].x , p_path[p_pathPosition].y);
}
}
}
// callback for when a unit is chasing an other unit
private static method callback_2(){
thistype this = GetTimerData(GetExpiredTimer());
real distance, x, y;
static if(DESTROY_ON_DEATH){
if (!UnitAlive(p_u)){
destroy();
return;
}
}
static if(DISABLE_ON_DEATH){
if (!UnitAlive(p_u)){
PauseTimer(clock);
p_enabled = false;
return;
}
}
x = GetUnitX(p_u); y = GetUnitY(p_u);
distance = SquareRoot((xReturn-x)*(xReturn-x) + (yReturn-y)*(yReturn-y));
// unit returned to path, it can continue patroling
if(!isChasing && distance <= MAX_RANGE){
TimerStart(clock, INTERVAL, true, function thistype.callback);
}
// unit is out of max range, it gets ordered moving back
if(distance > p_guardDistance){
isChasing = false;
IssuePointOrderById(p_u, PatrolOrder.MOVE, xReturn, yReturn);
}
// unit is not chasing anymore, so return
if (GetUnitCurrentOrder(p_u) == null){
isChasing = false;
IssuePointOrderById(p_u, PatrolOrder.MOVE, xReturn, yReturn);
}
}
private static method onTargetAquired() -> boolean{
thistype this = GetUnitId(GetTriggerUnit());
if (!isChasing){
xReturn = GetUnitX(p_u);
yReturn = GetUnitY(p_u);
}
isChasing = true;
TimerStart(clock, INTERVAL, true, function thistype.callback_2);
return false;
}
static method create(unit u, PatrolPath pp, PatrolType pt, PatrolOrder po, real gd, boolean r) -> thistype{
thistype this;
if(integer(pt) < 1 || integer(pt) > 3){
debug BJDebugMsg(thistype.create.name + ": unit with id " + I2S(GetHandleId(u)) + " tries to apply an invalid PatrolType.");
return 0;
}
if(!(po == PatrolOrder.MOVE || po == PatrolOrder.ATTACK)){
debug BJDebugMsg(thistype.create.name + ": unit with id" + I2S(GetHandleId(u)) + " tries to apply an invalid PatrolOrder.");
return 0;
}
if(IsUnitInGroup(u, Group)){
debug BJDebugMsg(thistype.create.name + ": unit with id" + I2S(GetHandleId(u)) + " already is a PatrolUnit.");
return 0;
}
this = GetUnitId(u);
clock = NewTimerEx(this);
p_u = u;
p_order = po;
p_path = pp;
p_patrolType = pt;
p_reverse = r;
p_enabled = true;
p_handler = null;
isChasing = false;
p_guardDistance = gd;
GroupAddUnit(Group, p_u);
if(po == PatrolOrder.ATTACK){
guardHandler = CreateTrigger();
TriggerAddCondition(guardHandler, Condition(function thistype.onTargetAquired));
TriggerRegisterUnitEvent(guardHandler, u, EVENT_UNIT_ACQUIRED_TARGET);
}
if(r){
p_pathPosition = p_path.size()-1;
incrementer = -1;
}
else{
p_pathPosition = 0;
incrementer = 1;
}
TimerStart(clock, INTERVAL, true, function thistype.callback);
return this;
}
method operator patrolType() -> PatrolType{
return p_patrolType;
}
method operator patrolType=(PatrolType p){
if(integer(p) > 0 && integer(p) <= PatrolType.MAXIMUM_AMOUNT){
p_patrolType = p;
}
else{
debug BJDebugMsg("Patrol: operator patrolType=: " + ": instance " + I2S(this) + " tries to apply an invalid PatrolType.");
}
}
method operator reverse() -> boolean{
return p_reverse;
}
method operator reverse=(boolean flag){
if(flag != p_reverse){
incrementer = -incrementer;
p_reverse = flag;
}
}
method operator path() -> PatrolPath{
return p_path;
}
method operator path=(PatrolPath p){
p_path = p;
if(reverse){
p_pathPosition = p_path.size()-1;
incrementer = -1;
}
else{
p_pathPosition = 0;
incrementer = 1;
}
}
// this exists so the user has no chance to "null" the
// unit variable in public access.
method operator unit()-> unit{
return p_u;
}
method operator pathPosition()-> integer{
return p_pathPosition;
}
method operator pathPosition=(integer i){
if (0 <= i && i <= (p_path.size()-1))
p_pathPosition = i;
else
debug BJDebugMsg("Patrol: operator pathPosition=: " + ": instance " + I2S(this) + " tries to apply an invalid pathPosition.");
}
method operator guardDistance() -> real{
return p_guardDistance;
}
method operator guardDistance=(real r){
p_guardDistance = r;
}
method operator order()-> PatrolOrder{
return p_order;
}
method operator order=(PatrolOrder p){
if (p == PatrolOrder.MOVE || p == PatrolOrder.ATTACK){
if(p == PatrolOrder.ATTACK && p_order != p){
guardHandler = CreateTrigger();
TriggerAddCondition(guardHandler, Condition(function thistype.onTargetAquired));
TriggerRegisterUnitEvent(guardHandler, p_u, EVENT_UNIT_ACQUIRED_TARGET);
}
else if(p_order == PatrolOrder.ATTACK && p == PatrolOrder.MOVE){
DestroyTrigger(p_handler);
p_handler = null;
}
p_order = p;
}
else{
debug BJDebugMsg("Patrol: operator order=: " + ": instance " + I2S(this) + " tries to apply an invalid PatrolOrder.");
}
}
method registerPatrolUnit(boolexpr be) -> triggercondition{
if(p_handler == null){
p_handler = CreateTrigger();
}
return TriggerAddCondition(p_handler, be);
}
method unregisterPatrolUnit(triggercondition tc){
TriggerRemoveCondition(p_handler, tc);
}
method operator enabled() -> boolean{
return p_enabled;
}
method operator enabled=(boolean flag){
if(flag && !(p_enabled)){
TimerStart(clock, INTERVAL, true, function thistype.callback);
if(p_order == PatrolOrder.ATTACK){
EnableTrigger(guardHandler);
}
}
else if(!(flag) && p_enabled){
PauseTimer(clock);
if(p_order == PatrolOrder.ATTACK){
DisableTrigger(guardHandler);
}
}
p_enabled = flag;
}
static method operator enabledAll=(boolean flag){
unit fog;
fog = FirstOfGroup(Group);
while(fog != null){
thistype(GetUnitId(fog)).enabled = flag;
GroupAddUnit(Backup,fog);
GroupRemoveUnit(Group,fog);
fog = FirstOfGroup(Group);
}
Temp = Group;
Group = Backup;
Backup = Temp;
}
static method operator[](unit u)-> thistype{
if(!IsUnitInGroup(u, Group)){
debug BJDebugMsg("Patrol: operator []: " + ": unit with id " + I2S(GetHandleId(u)) + " is not a PatrolUnit yet.");
return 0;
}
return GetUnitId(u);
}
static method register(boolexpr be) -> triggercondition{
return TriggerAddCondition(thistype.handler, be);
}
static method unregister(triggercondition tc){
TriggerRemoveCondition(thistype.handler, tc);
}
static method operator currentPatrolUnit()->thistype{
return p_patrolUnit;
}
// wow, static if's aren't allowed globaly in Zinc :(
private static method onDeindex()->boolean{
if (IsUnitInGroup(GetIndexedUnit(), Group)) {
thistype(GetIndexedUnitId()).destroy();
}
return false;
}
// wow, static if's still aren't allowed globaly in Zinc :(
private static method onInit(){
OnUnitDeindex(function thistype.onDeindex);
}
} // end of struct
public function PatrolPathHardDestroy (PatrolPath p){
integer i = p.size() - 1;
while(i >= 0){
p[i].destroy();
i = i - 1;
}
p.destroy();
}
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
//! zinc
library PatrolGUI requires Patrol /* v1.1 -- hiveworkshop.com/threads/patrol-waypoint-system.276391/ */{
PatrolPath tempPath = 0;
Table table;
function onRegister(){
if(GetHandleId(udg_PATROL_Point) == 0){
debug BJDebugMsg("ERROR: PatrolGUI - Register - Point is invalid or does not exist.");
return;
}
if (tempPath == 0)
tempPath = PatrolPath.create();
tempPath.push(PatrolPoint.create(GetLocationX(udg_PATROL_Point), GetLocationY(udg_PATROL_Point)));
}
function onCreate(){
if(udg_PATROL_Order != udg_PATROL_ORDER_MOVE && udg_PATROL_Order != udg_PATROL_ORDER_ATTACK){
debug BJDebugMsg("ERROR: PatrolGUI - Create - Invalid PatrolOrder");
return;
}
if(udg_PATROL_PatrolType < 1 || udg_PATROL_PatrolType > 3){
debug BJDebugMsg("ERROR: PatrolGUI - Create - Invalid PatrolType.");
return;
}
if(tempPath == 0){
debug BJDebugMsg("ERROR: PatrolGUI - Create - No Points are registered.");
return;
}
PatrolUnit.create(udg_PATROL_Unit, tempPath, udg_PATROL_PatrolType, OrderId(udg_PATROL_Order), udg_PATROL_GuardDistance, false).enabled = udg_PATROL_Enabled;
table.integer[GetHandleId(udg_PATROL_Unit)] = tempPath;
tempPath = 0;
}
function onDestroy(){
integer id = GetHandleId(udg_PATROL_Unit);
if(table.integer.has(id)){
PatrolPathHardDestroy(PatrolPath(table.integer[id]));
table.integer.remove(id);
}
else
debug BJDebugMsg("ERROR: PatrolGUI - Destroy - Unit is no PatrolUnit.");
}
function onGet(){
PatrolUnit this = PatrolUnit[udg_PATROL_Unit];
udg_PATROL_Enabled = this.enabled;
udg_PATROL_PatrolType = this.patrolType;
udg_PATROL_Order = OrderId2String(this.order);
udg_PATROL_GuardDistance = this.guardDistance;
}
function onSet(){
PatrolUnit this;
if(udg_PATROL_Order != udg_PATROL_ORDER_MOVE && udg_PATROL_Order != udg_PATROL_ORDER_ATTACK){
debug BJDebugMsg("ERROR: PatrolGUI - SetData - Invalid PatrolOrder");
return;
}
if(udg_PATROL_PatrolType < 1 || udg_PATROL_PatrolType > 3){
debug BJDebugMsg("ERROR: PatrolGUI - SetData - Invalid PatrolType.");
return;
}
this = PatrolUnit[udg_PATROL_Unit];
this.enabled = udg_PATROL_Enabled;
this.patrolType = udg_PATROL_PatrolType;
this.order = OrderId(udg_PATROL_Order);
this.guardDistance = udg_PATROL_GuardDistance;
}
function init(){
udg_PATROL_TYPE_NORMAL = PatrolType.NORMAL;
udg_PATROL_TYPE_BACKWARD = PatrolType.BACKWARD;
udg_PATROL_TYPE_LOOP = PatrolType.LOOP;
udg_PATROL_ORDER_MOVE = "move";
udg_PATROL_ORDER_ATTACK = "attack";
DestroyTimer(GetExpiredTimer());
}
function onInit(){
table = Table.create();
TimerStart(CreateTimer(), 0, false, function init);
udg_PATROL_Create = CreateTrigger();
udg_PATROL_Destroy = CreateTrigger();
udg_PATROL_RegisterPoint = CreateTrigger();
udg_PATROL_GetData = CreateTrigger();
udg_PATROL_SetData = CreateTrigger();
TriggerAddAction(udg_PATROL_Create, function onCreate);
TriggerAddAction(udg_PATROL_RegisterPoint, function onRegister);
TriggerAddAction(udg_PATROL_Destroy, function onDestroy);
TriggerAddAction(udg_PATROL_GetData, function onGet);
TriggerAddAction(udg_PATROL_SetData, function onSet);
}
}
//! endzinc
//TESH.scrollpos=30
//TESH.alwaysfold=0
//! zinc
library PatrolDemo requires Patrol{
PatrolPath Route1;
PatrolPath Route2;
PatrolPath Route3;
// these 2 will form our complex patrol
PatrolPath Route4;
PatrolPath Route5;
// Sqaure route
function InitRoute1(){
// First we create a new PatrolPath
Route1 = PatrolPath.create();
// Now we create and directly add some PatrolPoints
// We use the vector function "push"
Route1.push(PatrolPoint.create(0, 0));
Route1.push(PatrolPoint.create(500, 0));
Route1.push(PatrolPoint.create(500, 500));
Route1.push(PatrolPoint.create(0, 500));
}
// Triangle route
function InitRoute2(){
Route2 = PatrolPath.create();
// We also can use the vector API like this, in one line
Route2.push(PatrolPoint.create(-500, 0)).push(PatrolPoint.create(-500, 500)).push(PatrolPoint.create(0, 500));
}
// Line route
function InitRoute3(){
Route3 = PatrolPath.create();
Route3.push(PatrolPoint.create(0, -700)).push(PatrolPoint.create(0, 0));
}
// Line route
function InitRoute4(){
Route4 = PatrolPath.create();
Route4.push(PatrolPoint.create(-400, -100)).push(PatrolPoint.create(400, -100));
}
// Line route
function InitRoute5(){
Route5 = PatrolPath.create();
Route5 = Route5.push(PatrolPoint.create(-400, -600)).push(PatrolPoint.create(400, -600));
}
// Pause on Esc
boolean enabled = true;
function onEsc(){
enabled = !enabled;
PatrolUnit.enabledAll = enabled;
}
// Will be our complex patrol (see also later)
function complexPatrolEvent()-> boolean{
// instance who triggered the event:
PatrolUnit this = PatrolUnit.currentPatrolUnit;
// when the current path position is the end of Route4,
// we move the unit to start of Route5 and define the new Route
if(this.path[this.pathPosition] == Route4.back()){
this.path = Route5;
SetUnitX(this.unit, Route5.front().x);
SetUnitY(this.unit, Route5.front().y);
}
// when the current path position is the end of Route5,
// we move the unit to start of Route4 and define the new Route
if(this.path[this.pathPosition] == Route5.back()){
this.path = Route4;
SetUnitX(this.unit, Route4.front().x);
SetUnitY(this.unit, Route4.front().y);
}
// !! You need to learn the VectorT API, like back() and front() to properly work with this.
return false;
}
function onInit(){
trigger t = CreateTrigger();
player p = GetLocalPlayer();
unit u;
InitRoute1();
InitRoute2();
InitRoute3();
InitRoute4();
InitRoute5();
// To each route we apply 2 units.
// One of them will run normaly, and the other one reverse.
// For us to remind how the create function looks:
//! novjass
static method create(unit u, PatrolPath pp, PatrolType pt, PatrolOrder po, real guardDistance, boolean reverse) returns thistype
//! endnovjass
// footman is for normal patrol
u = CreateUnit(p, 'hfoo', 0, 0, 0);
UnitAddAbility(u, 'Aeth'); //ghost to disable collision
PatrolUnit.create(u, Route1, PatrolType.NORMAL, PatrolOrder.MOVE, 0, false);
u = CreateUnit(p, 'hfoo', 0, 500, 0);
UnitAddAbility(u, 'Aeth'); //ghost to disable collision
PatrolUnit.create(u, Route1, PatrolType.NORMAL, PatrolOrder.MOVE, 0, true);
// gyrphon is for backward patrol
u = CreateUnit(p, 'hgry', -500, 0, 0);
UnitAddAbility(u, 'Aeth');
PatrolUnit.create(u, Route2, PatrolType.BACKWARD, PatrolOrder.MOVE, 0, false);
u = CreateUnit(p, 'hgry', 0, 500, 0);
UnitAddAbility(u, 'Aeth');
PatrolUnit.create(u, Route2, PatrolType.BACKWARD, PatrolOrder.MOVE, 0, true);
// knight is for loop patrol
u = CreateUnit(p, 'hkni', 0, -700, 0);
UnitAddAbility(u, 'Aeth');
PatrolUnit.create(u, Route3, PatrolType.LOOP, PatrolOrder.MOVE, 0, false);
u = CreateUnit(p, 'hkni', 0, 0, 0);
UnitAddAbility(u, 'Aeth');
PatrolUnit.create(u, Route3, PatrolType.LOOP, PatrolOrder.MOVE, 0, true);
// For our last example, a complex patrol,
// we create only one unit, which runs not reverse.
// Technicaly we could also run complex reverse patrols,
// but this would require us to write more logics inside
// the "function specialPatrolEvent", and we don't want this.:)
// p.s. I call it "complex patrol", because it is a combination of mutiple PatrolPaths
// spell breaker is for complex patrol
u = CreateUnit(p, 'hspt', -400, -100, 0);
UnitAddAbility(u, 'Aeth');
PatrolUnit.create(u, Route4, PatrolType.NORMAL, PatrolOrder.MOVE, 0, false);
PatrolUnit[u].registerPatrolUnit(Filter(function complexPatrolEvent)); // fires only for this instance :)
TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_END_CINEMATIC);
TriggerAddAction(t, function onEsc);
BJDebugMsg("Press 'Esc' button to pause/unpause all patrol units.");
}
}
//! endzinc
//TESH.scrollpos=80
//TESH.alwaysfold=0
/*****************************************************************************
*
* Vector<T> v1.1.6.6
* by Bannar aka Spinnaker
*
* Dynamic contiguous array.
*
******************************************************************************
*
* Requirements:
*
* Table by Bribe
* hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
* Alloc - choose whatever you like
*
******************************************************************************
*
* Implementation:
*
* macro DEFINE_STRUCT_VECTOR takes ACCESS, NAME, TYPE
*
* macro DEFINE_VECTOR takes ACCESS, NAME, TYPE
*
* ACCESS - encapsulation, choose retriction access
* NAME - name of vector type
* TYPE - type of values stored
*
******************************************************************************
*
* struct API:
*
* General:
*
* | static method create takes nothing returns thistype
* | default ctor
* |
* | static method operator [] takes thistype vec returns thistype
* | copy ctor
* |
* | method destroy takes nothing returns nothing
* | default dctor
* |
* | method empty takes nothing returns boolean
* | checks whether the vector is empty
* |
* | method size takes nothing returns integer
* | returns size of a vector
*
*
* Access:
*
* | method operator [] takes integer index returns $TYPE$
* | returns item at position index
* |
* | method operator []= takes integer index, $TYPE$ value returns nothing
* | sets item at index to value
* |
* | method front takes nothing returns $TYPE$
* | retrieves first element
* |
* | method back takes nothing returns $TYPE$
* | retrieves last element
* |
* | method data takes nothing returns Table
* | returns the underlying Table object
*
*
* Modifiers:
*
* | method clear takes nothing returns nothing
* | performs a flush operation on data table
* |
* | method push takes $TYPE$ value returns thistype
* | adds elements to the end
* |
* | method pop takes nothing returns thistype
* | removes the last element
* |
* | method assign takes integer count, $TYPE$ value returns thistype
* | assigns count elements replacing current data
* |
* | method insert takes integer pos, integer count, $TYPE$ value returns thistype
* | inserts count elements before position pos
* |
* | method erase takes integer pos, integer count returns thistype
* | erase count elements starting at position pos
*
*
*****************************************************************************/
library VectorT requires Table, Alloc
// Run here any global vector types you want to be defined.
//! runtextmacro DEFINE_VECTOR("", "IntegerVector", "integer")
//! textmacro_once DEFINE_STRUCT_VECTOR takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$ extends array
private delegate IntegerVector parent
method operator[] takes integer index returns $TYPE$
return parent[index]
endmethod
method front takes nothing returns $TYPE$
return parent.front()
endmethod
method back takes nothing returns $TYPE$
return parent.back()
endmethod
static method create takes nothing returns thistype
local thistype this = IntegerVector.create()
set parent = this
return this
endmethod
endstruct
//! endtextmacro
//! textmacro_once DEFINE_VECTOR takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$ extends array
private Table table
private integer length
implement Alloc
private method seT takes integer index, $TYPE$ value returns nothing
set table.$TYPE$[index] = value
endmethod
private method get takes integer index returns $TYPE$
return table.$TYPE$[index]
endmethod
private method assert_pos takes integer pos, string f returns boolean
debug if ( pos < 0 and pos >= length ) then
debug call DisplayTimedTextFromPlayer(GetLocalPlayer(),0,0,60,"$NAME$::assert_pos failed at "+f+" for instance "+I2S(this)+". Invalid index at position: "+I2S(pos)+".")
debug endif
return ( pos >= 0 and pos < length )
endmethod
private method assert_range takes integer pos, string f returns boolean
debug if ( pos < 0 or pos > length ) then
debug call DisplayTimedTextFromPlayer(GetLocalPlayer(),0,0,60,"$NAME$::assert_range failed at "+f+" for instance "+I2S(this)+". Invalid iterator at position: "+I2S(pos)+".")
debug endif
return ( pos >= 0 and pos <= length )
endmethod
method operator [] takes integer index returns $TYPE$
debug if not assert_pos(index, "operator []") then
debug return get(-1)
debug endif
return get(index)
endmethod
method operator []= takes integer index, $TYPE$ value returns nothing
debug if not assert_pos(index, "operator []=") then
debug return
debug endif
call seT(index, value)
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set table = Table.create()
set length = 0
return this
endmethod
method empty takes nothing returns boolean
return length == 0
endmethod
method size takes nothing returns integer
return length
endmethod
static method operator [] takes thistype vec returns thistype
local thistype this = create()
loop
exitwhen length >= vec.size()
call seT(length, vec[length])
set length = length + 1
endloop
return this
endmethod
method clear takes nothing returns nothing
set length = 0
call table.flush()
endmethod
method destroy takes nothing returns nothing
call clear()
call table.destroy()
set table = 0
call deallocate()
endmethod
method front takes nothing returns $TYPE$
return this[0]
endmethod
method back takes nothing returns $TYPE$
return this[length-1]
endmethod
method data takes nothing returns Table
return this
endmethod
method push takes $TYPE$ value returns thistype
call seT(length, value)
set length = length + 1
return this
endmethod
method pop takes nothing returns thistype
if ( length > 0 ) then
set length = length - 1
call table.$TYPE$.remove(length)
call table.real.remove(0)
endif
return this
endmethod
method assign takes integer count, $TYPE$ value returns thistype
if ( count > 0 ) then
call clear()
loop
exitwhen length >= count
call push(value)
endloop
endif
return this
endmethod
method insert takes integer pos, integer count, $TYPE$ value returns thistype
local integer i
if assert_range(pos, "insert") then
if ( count > 0 ) then
set length = length + count
set i = length - 1
loop
exitwhen i < (pos + count)
call seT(i, get(i-count))
set i = i - 1
endloop
set i = 0
loop
exitwhen i >= count
call seT(pos+i, value)
set i = i + 1
endloop
endif
endif
return this
endmethod
method erase takes integer pos, integer count returns thistype
if assert_pos(pos, "erase") then
if ( count > 0 ) then
if ( pos + count > length ) then
set count = length - pos
endif
set pos = pos + count
loop
exitwhen pos >= length
call seT(pos-count, get(pos))
set pos = pos + 1
endloop
loop
exitwhen count <= 0
call pop()
set count = count - 1
endloop
endif
endif
return this
endmethod
endstruct
//! endtextmacro
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Alloc /* v1.3.1.1
*************************************************************************************
*
* */ uses /*
*
* */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
* */ optional MemoryAnalysis /*
*
*************************************************************************************
*
* Minimizes code generation and global variables while maintaining
* excellent performance.
*
* local thistype this = recycler[0]
*
* if (recycler[this] == 0) then
* set recycler[0] = this + 1
* else
* set recycler[0] = recycler[this]
* endif
*
************************************************************************************
*
* module Alloc
*
* static method allocate takes nothing returns thistype
* method deallocate takes nothing returns nothing
*
* The Following Require Error Message To Be In The Map
* --------------------------------------------------------
*
* debug readonly boolean allocated
*
* The Following Require Memory Analysis To Be In The Map
* --------------------------------------------------------
*
* debug readonly integer monitorCount
* - the amount of global memory being monitored by this
* debug readonly integer monitorString
* - gets a string representation of all global memory being monitored by this
* debug readonly integer address
* - global memory address for debugging
* - used with monitor and stopMonitor
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
* debug method monitor takes string label, integer address returns nothing
* - monitor a global memory address with a label
* - used to identify memory leaks
* - should be memory that ought to be destroyed by the time this is destroyed
* debug method stopMonitor takes integer address returns nothing
* - stops monitoring global memory
* debug method stopMonitorValue takes handle monitoredHandle returns nothing
* - stops monitoring handle values
*
* The Following Are Used To Monitor Handle Values
*
* debug method monitor_widget takes string label, widget handleToTrack returns nothing
* debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
* debug method monitor_item takes string label, item handleToTrack returns nothing
* debug method monitor_unit takes string label, unit handleToTrack returns nothing
* debug method monitor_timer takes string label, timer handleToTrack returns nothing
* debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
* debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
* debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
* debug method monitor_force takes string label, force handleToTrack returns nothing
* debug method monitor_group takes string label, group handleToTrack returns nothing
* debug method monitor_location takes string label, location handleToTrack returns nothing
* debug method monitor_rect takes string label, rect handleToTrack returns nothing
* debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
* debug method monitor_effect takes string label, effect handleToTrack returns nothing
* debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
* debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
* debug method monitor_quest takes string label, quest handleToTrack returns nothing
* debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
* debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
* debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
* debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
* debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
* debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
* debug method monitor_button takes string label, button handleToTrack returns nothing
* debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
* debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
* debug method monitor_image takes string label, image handleToTrack returns nothing
* debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
* debug method monitor_region takes string label, region handleToTrack returns nothing
* debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
* debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
*
************************************************************************************/
module Alloc
/*
* stack
*/
private static integer array recycler
static if LIBRARY_MemoryAnalysis then
debug private MemoryMonitor globalAddress
debug method operator address takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "address", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress
debug endmethod
endif
/*
* allocation
*/
static method allocate takes nothing returns thistype
local thistype this = recycler[0]
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 8192, "Alloc", "allocate", "thistype", 0, "Overflow.")
endif
if (recycler[this] == 0) then
set recycler[0] = this + 1
else
set recycler[0] = recycler[this]
endif
static if LIBRARY_ErrorMessage then
debug set recycler[this] = -1
endif
static if LIBRARY_MemoryAnalysis then
debug set globalAddress = MemoryMonitor.allocate("thistype")
endif
return this
endmethod
method deallocate takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(recycler[this] != -1, "Alloc", "deallocate", "thistype", this, "Attempted To Deallocate Null Instance.")
endif
static if LIBRARY_MemoryAnalysis then
debug call globalAddress.deallocate()
debug set globalAddress = 0
endif
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
static if LIBRARY_MemoryAnalysis then
debug method monitor takes string label, integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor(label, address)
debug endmethod
debug method stopMonitor takes integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitor(address)
debug endmethod
debug method stopMonitorValue takes handle monitoredHandle returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitorValue", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitorValue(monitoredHandle)
debug endmethod
debug method operator monitorCount takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorCount", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorCount
debug endmethod
debug method operator monitorString takes nothing returns string
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorString", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorString
debug endmethod
debug method monitor_widget takes string label, widget handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_widget", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_widget(label, handleToTrack)
debug endmethod
debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_destructable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_destructable(label, handleToTrack)
debug endmethod
debug method monitor_item takes string label, item handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_item", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_item(label, handleToTrack)
debug endmethod
debug method monitor_unit takes string label, unit handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unit", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unit(label, handleToTrack)
debug endmethod
debug method monitor_timer takes string label, timer handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timer", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timer(label, handleToTrack)
debug endmethod
debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_trigger", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_trigger(label, handleToTrack)
debug endmethod
debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggercondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggercondition(label, handleToTrack)
debug endmethod
debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggeraction", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggeraction(label, handleToTrack)
debug endmethod
debug method monitor_force takes string label, force handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_force", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_force(label, handleToTrack)
debug endmethod
debug method monitor_group takes string label, group handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_group", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_group(label, handleToTrack)
debug endmethod
debug method monitor_location takes string label, location handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_location", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_location(label, handleToTrack)
debug endmethod
debug method monitor_rect takes string label, rect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_rect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_rect(label, handleToTrack)
debug endmethod
debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_boolexpr", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_boolexpr(label, handleToTrack)
debug endmethod
debug method monitor_effect takes string label, effect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_effect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_effect(label, handleToTrack)
debug endmethod
debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unitpool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unitpool(label, handleToTrack)
debug endmethod
debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_itempool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_itempool(label, handleToTrack)
debug endmethod
debug method monitor_quest takes string label, quest handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_quest", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_quest(label, handleToTrack)
debug endmethod
debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_defeatcondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_defeatcondition(label, handleToTrack)
debug endmethod
debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timerdialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timerdialog(label, handleToTrack)
debug endmethod
debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_leaderboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_leaderboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboarditem", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboarditem(label, handleToTrack)
debug endmethod
debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_dialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_dialog(label, handleToTrack)
debug endmethod
debug method monitor_button takes string label, button handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_button", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_button(label, handleToTrack)
debug endmethod
debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_texttag", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_texttag(label, handleToTrack)
debug endmethod
debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_lightning", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_lightning(label, handleToTrack)
debug endmethod
debug method monitor_image takes string label, image handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_image", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_image(label, handleToTrack)
debug endmethod
debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_ubersplat", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_ubersplat(label, handleToTrack)
debug endmethod
debug method monitor_region takes string label, region handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_region", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_region(label, handleToTrack)
debug endmethod
debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_fogmodifier", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_fogmodifier(label, handleToTrack)
debug endmethod
debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_hashtable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_hashtable(label, handleToTrack)
debug endmethod
static if DEBUG_MODE then
//! runtextmacro optional MEMORY_ANALYSIS_STATIC_FIELD_NEW("recycler")
static method calculateMemoryUsage takes nothing returns integer
return calculateAllocatedMemory__recycler()
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
return allocatedMemoryString__recycler()
endmethod
endif
endif
/*
* analysis
*/
static if LIBRARY_ErrorMessage then
debug method operator allocated takes nothing returns boolean
debug return recycler[this] == -1
debug endmethod
endif
/*
* initialization
*/
private static method onInit takes nothing returns nothing
set recycler[0] = 1
endmethod
endmodule
endlibrary
//TESH.scrollpos=27
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* 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.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* 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.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
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==ARRAY_SIZE) 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
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=4
//TESH.alwaysfold=0
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.1, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
///! external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines except for UnitDexRemove
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeidex takes code func returns triggercondition
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
* - All public functions are inlined except UnitDexRemove.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 0
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
// Index preplaced units
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
// run init triggers
set i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TimerStart(CreateTimer(), 0, false, function thistype.onGameStart)
endmethod
endmodule
endlibrary