1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. The mythological era has spawned some interesting characters around. Check them out and be sure to vote for them in the 30th Poll of the Texturing Contest.
    Dismiss Notice
  3. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  4. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice

Patrol System v1.6

Submitted by Chaosy, IcemanBo
This bundle is marked as approved. It works and satisfies the submission rules.
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.

Gif

[​IMG]
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


There is a normal, vJass version, and an addon which is made for GUI usage.
The for GUI made library internaly works with the normal vJass version, so you still might need required libraries.
For reading GUI file example you need to open the demo map.

Code (vJASS):

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

 


Code (vJASS):

//! 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
 


Changelog

v1.4 to v1.5:
- unit collision is no longer considered
- formated movement is now allowed


Keywords:
Patrol, Waypoint, MUI, system, jass, vjass, point, walk
Previews
Contents

Patrol System (Map)

Reviews
Moderator
19:40, 15th Mar 2016 BPower: Almia mentioned a few important things. I'll write a review when soon TM takes place.
Wietlol
API Madness:static method register(boolexpr be) returns triggercondition static method unregister(triggercondition tc) register can take in a "code" variable instead (as long as the functions arent inlined) Optimization:private timer clock; clock...
Wietlol
I suppose we can trust the user to not mess up :D Status: Approved
  1. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    I think yes.


    PatrolPoint:

    One technicaly can use the same PatrolPoints for multiple PatrolPaths, and also change x/y of a point at any time (because it's public).
    I thought of it can be good in some cases, and the standard vector destroy method (PatrolPath is a vectorT) would anyways only destroy the vector itself,
    and not all data structed that is binded to it. So I just thought adding a new function which, when the user really does not need the PatrolPoints anymore, then he
    can use this hard destroy method.
    There is also this "_exists" protection so when a user maybe uses a hard destroy function on 2 PatrolPaths which share some same PatrolPoints, so that these PatrolPoints don't get deallocated twice.

    Yes, "type" is better, will change.

    "NORMAL" is actually a loop, too, yes. Haven't really thought about it. Though, maybe one could argue the very last move order (from FinalPoints to StartPoint)
    is not directly defined in the PatrolPath, and so it's maybe not really part of the Patrol anymore. So it is not simple a LOOP of the PatrolPath, but it is a LOOP of
    the PatrolPath + MoveFinal-MoveStart. And here, the actual "LOOP" is not a circle, but a 100% loop of the actual PatrolPath. ;D Does it make sense?

    I would need to write a new data structure, maybe a list, being able to loop through it, which I actually don't really needed.
    I also don't need something like list access, or "next" or so, so I only would need it for looping. I thought using timer is a good (but maybe not faster, when many instances) solution.
    (it looks a bit cleaner, too)

    UnitDex also saves allocate() and deallocate(). ;D I started to make non-optional actually, because I peronaly would force everyone to use used libraries.^^
    But Chaosy likes to have less dependency, so they are optional with TimerUtils. Would you personaly make them required or remove them?

    Good catch, I probably should.^^

    Update soon.:)
     
  2. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    aww. "type" is reserved keyword. :s

    So I cannot make operators with it, which sucks. Does maybe someone know a good alternative? Else I guess I would stay with "state".
     
  3. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Update. And the method operator is now just called "patrolType" instead of "type", one can live with it.
     
  4. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    A likely use case of patrolling units would be to create guard units that the player would have to kill or pass undetected.
    If the move order makes the guards not attack/respond to other units then it sort of defeats the whole purpose?

    I think it would be best if the patrolling units would attack and chase for a certain [configurable] distance and then go back to their patrolling.
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    But it's configurable:
    Code (vJASS):
    //=================== --------- Config --------- ====================  
     
        integer ORDER_ID            = 851986;  // The order that the unit will receive to walk to the next point.
        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.
        boolean DESTROY_ON_DEATH    = false;   // When a PatrolUnit dies, the instance will be automaticaly destroyed.
                                               
       
        // move id    = 851986
        // attack id  = 851983
       
    //=================== -------------------------- ====================

    attack id is commented below.

    I think patrols is often used in slide or maze maps, and there, they usually just "walk" and don't attack. But it's no problem if they change it.

    When it's set to "attack" they will fight with the enemy, and automaticaly return to their patrol afterwerds when their current order gets "null".
     
  6. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    I see.

    It seems when the order is to attack (851983) the patrolling unit will chase an enemy unit forever, i.e the user would have to write extra code to check if a patrolling unit is too far away from its current point and if so order it to go back.

    Its configurable globaly, although I am not sure it makes sense to have both patrolling units that respond to attacks and chase units and patrolling units that don't respond to attacks and don't chase.
     
  7. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    You want feature that when a unit has a certain distance or bigger to the PatrolPoint, then the system forces the unit to walk to the patrol point (configurable).

    And ^this feature and orderId setable for each unit instead globaly?

    edit:

    what to do if order is "attack", and the distance between two PatrolPoints is by default bigger than "allowed Max_Distance"?

    then I wold force the unit to "walk" to next point, even it should use "attack", and is just normaly patroling

    edit2:

    with using InCombat library it would maybe work though. hm... :s

    edit3:

    [System] Combat State uses Event (extra requirment) and what is troublesome, an other unit indexer :(
     
    Last edited: Nov 20, 2016
  8. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    This is not true if the owner of the unit is PLAYER_NEUTRAL_AGGRESSIVE (creeps), the unit will chase until goes beyond the guard return distance (1000, in gameplay constats).

    The distance between patrolling points is arbitrary, my point is to add a "guard return distance" parameter to the system that would also work for units that are not owned by PLAYER_NEUTRAL_AGGRESSIVE player and that distance could be unit specific, not global like the one from the gameplay constants.
    Of course you could decide that the built-in "guard return distance" for PLAYER_NEUTRAL_AGGRESSIVE is good enough and simply add that to the documentation =).
     
  9. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Have you read my concern where it may get troublesome?

    Scenario:

    Distance between two patrolpoints = 2000
    MAX_ALLOWED_DISTANCE = 1000
    Order = "attack"

    now the system will think the unit is out of range, and so it will force the unit to return ("move") to next PatrolPoint,
    even it is normaly walking on the path. Actually the order should be "attack", and not "move". I would need to check for the unit distance to the PatrolLine,
    each single callback call, for each registered unit, which is very much operations, or use an extra CombatState library, to check if unit is OutOfRange AND in combat.
     
  10. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    Not sure I follow, why the next path position and not the current (
            private integer p_pathPosition;    // Current position in PatrolPath vector
    )?

    Anyway... there is the EVENT_UNIT_ACQUIRED_TARGET event that you might be able to use. The system will force the unit to return to its current (or nearest, or the position that it was when it acquired the target) path position only if its too far away and is chasing an enemy unit.

    If the order is attack and not move the unit could reacquire its target and continue chasing?

    Like I said, if you don't want to bother, you can piggyback on Blizzard's PLAYER_NEUTRAL_AGGRESSIVE + guard return distance behaviour.
     
  11. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    next PatrolPoint is the point where the unit currently goes/aims for. I mean that.

    The problem is to distinguish if it is currently chasing a unit or not.

    In my example there no enemy in range at all. So there is nothing to chase. But the user wants having "attack" order for the case.
    But the system would think unit is out of range -> force "move" back to route and not "attack".

    But I honestly don't very understand how it should help the unit to stay on a patrolPath/certain line. The return behaviour for aggresive passives relates to one certain point, only, or?

    Please let's continue discussing, if I get it wrong, I also see a return-feature can be useful, but I really don't understand atm how to optimaly implement good.
    For now I added the possiblity to define orderId per instance. attack/move. And I wrote an addon library for GUI usage.

    Updated.
     
  12. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    A patrolling unit goes into a chasing state when it EVENT_UNIT_ACQUIRED_TARGET, if it goes too far away from "its current path point (or nearest path point, or the position that it was when it acquired the target)" it goes into a "returning back state", after it reaches the point from which the chase started, it goes into a patrolling state again, etc.

    I don't have anything else to add.
     
  13. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    You keep repeating without countering mentioned concerns.
    Okay.
     
  14. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    Hm...

    A patrolling unit goes from one point of the path to the next via a patrolling order.(
    IssuePointOrderById(p_u, p_order, p_path[p_pathPosition].x, p_path[p_pathPosition].y);
    ).

    The patrolling unit starts its patrolling in a state called, say patrolling-state, if its patrolling order is not move but attack or smart and we have registered for it a EVENT_UNIT_ACQUIRED_TARGET (this event doesn't fire for the move order, how lucky =))
    we can detect when it has acquared an enemy target unit and put the patrolling unit into a chasing state.

    While being in a chasing state we check if its distance from some point (I guess it has to be either the nearest point of the patrolling path [which would be somewhat expensive to compute] or the point from which the patrolling unit started the chase [when the EVENT_UNIT_ACQUIRED_TARGET happend], now I understand your concern about the point being the current patrolling point, because the patrolling unit could immediately go into a "returning back state")
    is greater than our "guard return distance" parameter, and if so, we set the patrolling unit's state to that of "returning back state" and order it to move back with a move order regardless of the patrolling order.

    When the patrolling unit gets back to the point where the chasing started it goes again into a patrolling state and we order it via the patrolling order to go to its current patrol path point and continue its patrolling.
     
  15. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Tried to implement it as you said, thanks. I was not very sure to which point to return exactly, so I did this:
    I tried to experiment a bit and changed code here and there (though changed back to normal again), and so it became hard to maintain all changes with all optional libraries.
    I made all libraries as "required" then to stop the mess. Sorry, Chaosy.

    So updated to what Aniki suggested.
     
  16. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    Don't forget the GUI support, and gj! =)
     
  17. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Idk if it was a hidden hint, but thanks. ^^ The vjass demo was not up to date. New upload (just demo code fix).
     
  18. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    Well its definitely not free but you know... its better =).
     

    Attached Files:

  19. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Hm, I'm not very sure it's not just wanted going back to the point where the unit left patroling
     
  20. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,070
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Updated.

    @CHA_Owner , unit's collision should no longer be a thing, can you try if the issue is gone for you? If there is still a issue you can change the max distance in the config now.

    Also, there was a bug that @Tank-Commander found, which could appear with having "formated movement" enabled. The user can also set it in config now, if the units may have formated movement, or not.
     

    Attached Files: