Footman | - Normal Patrol , and in reverse form |
Gryphon | - Backward Patrol, and in reverse form |
Knight | - Loop Patrol, and in reverse form |
Spellbreaker | - Complex Patrol (2 paths combined) |
Whisp | - Normal Patrol |
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
//! 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