1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. The long-awaited results for Concept Art Contest #11 have finally been released!
    Dismiss Notice
  3. 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
  4. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  5. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  6. 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. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,485
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    It logically makes more sense to just keep it as
    <=
    . The chance of the dist being exactly detectRange^2 is extremely small, maybe not even possible. Nonetheless, it was just a suggestion :p
     
  2. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,184
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    I agree, and personally I would use
    <=
    since there is no performance penalty - but that doesn't change the fact that it doesn't matter.

    Surely it's possible, but it's more interesting that it makes no difference for the player. It also pales in comparison to the fact that radii mean different things in different contexts in warcraft 3. For example, a unit with endurance aura, radius 400 will reach different units than a group enumeration with radius 400.

    I speak from experience when I say that it's a bad habit to mix opinions/suggestions with facts and requirements. Believe me when I say that I have to actively think about it to stop from doing it myself.

    As a suggestion, perhaps separate your requirements (that lead the resource to be pending) from your suggestions (that are small quips), for example like so:

    Issues:

    The script has loads of leaks and it set my PC on fire.

    Other suggestions:

    It might describe developer intent better in the radius code to use
    <=
    .
     
  3. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Okay..

    1. Separating API and code is simply more clean in my opinion.
    2. Fixed
    3. Fixed
    4. It's supposed to mimic the original patrol, thus attack is the correct way of doing things.
    5. Can? Absolutely. Should? probably not. I personally like a more 'logical' API where the function name does what the name describes. calling Start(false) just looks weird to me.
    6. There used to be one, I think. I removed it for some reason though.
     
  4. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,485
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    >

    I was just trying to add some more configurability to the system :D


    If you're going to be allocating unique indexes for the struct, you should be dealloacting them as well. I would also maybe add an extra check when users use
    RemoveAt()
    that automatically deallocates the instance when the count reaches 0.
     
  5. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Time to get this shit approved.

    I added
    Code (vJASS):

                method destroy()
                {
                    this.deallocate();
                }


    That's all I need in a destroy function, yeah?

    edit: nope, that ain't gonna work because of me using some retarded indexing.
    Code (vJASS):

               
                method destroy()
                {
                    if(instances[index] != instances[max])
                    {
                        instances[index] = instances[max];
                    }
                    max -= 1;
                    this.deallocate();
                }
     
     
    Last edited: Oct 3, 2016
  6. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,485
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    What happens if you are to use
    RemoveAt()
    on the last patrol instance? Shouldn't there be something like this:
     
  7. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Well, one may remove the last point and then add new ones to the same instance.
     
  8. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,485
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Well unless someone manually keeps track of the instances they remove and add, you wouldnt ever know you removed the last instance unless you physically look at the unit. I really think that check in the method would be golden :D we can get this approved if you add that.
     
  9. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    Code (vJASS):

                method destroy()
                {
                    if(instances[index] != instances[max])
                    {
                        instances[index] = instances[max];
                    }
                    max -= 1;
                    this.deallocate();
                }
     

    The above destroy method still won't work, try making 3 patrols:
    1 - footman
    2 - grunt
    3 - ghoul

    Now destroy the footman's patrol and the ghoul's patrol, i.e only leaving the grunt's patrol and see what happens. You should see that the grunt has stopped patrolling but the ghoul hasn't, although we did call .destroy on it's patrol.
     
  10. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Because I do not change the index, right?
    so I would also do:
    instances[index].index = instances[max].index ?
     
  11. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,055
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    • Timers should not run for nothing.
      You could solve this with individual timers, or with a help counter to count active instances.
    • Add and Remove methods can't be used properly. With current structure, the user has no real power which point to remove.
      It was good if the "Add" function returns a Point or so, and the "Remove" function does take this Point as argument and removes it properly from the list.
    • function "Start" is the very same as "Resume". You actually can take "Start", "Stop", and "Resume" together to
      method enabled takes boolean flag returs nothing
    • Please comment
      851983
      which order it is or use a descriptive variable.
    • The issue Aniki pointed out needs to be fixed.
    • constant definitons of
      [99]
      arrays might work for most cases, but dynamic lists would be superior.
    • A link to used TimerUtils would be useful.
    • Do you know maze maps where units spawn at first Patrol point again when reaching the end? It could be a useful flag to set.
      For example
      boolean circular
      -- true would mean it moves by itself, normaly to the first point, so it makes a full "circle".
      And if false, it would be a linear movement only in one direction, from start to end, and then it would be moved back instantly by triggers.
      This would maybe be a cool addition some might want to use.
     
  12. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    1. Will do
    2. What? the add function works just fine, otherwise the system would not work.
    3. I will see if I can merge them, I wont promise that I use that exact suggestion though.
    4. Will do
    5. Will do
    6. They would, but it would be insanely rare for them to be used effectively.
    7. Will do
    8. Hm. Not interested in doing that yet. I'd rather have it approved before I add more features that may contain further errors and thus delay approval.
     
  13. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,055
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Yes "add" should work. Hm, what I probably wanted to underline is that the user does not really have powers what point exactly to remove, and where to add one.

    For example I have Points[]: P1, P2, P3

    Now I add P4: P1, P2, P3, P4

    Now I Points[2] : P1, P4, P3 (!)
     
  14. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    I think I get what you mean.. that is indeed troublesome.
    I got a solution planned, I will remake the system a bit, I think it should fix it.

    edit: or so I thought. My new code does not work, sadly and I don't have the will to spend hours on debugging. You got my permission to send this to substandard.
     
    Last edited: Nov 7, 2016
  15. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,485
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Just report this resource when you want to get it moved back to Submissions.

    Moved to Substandard.
     
  16. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,055
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    If you agree, I can work on the submission, too, and we might just be co-authors. :)
     
  17. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    I wouldn't mind.
    What I had in mind was to have another struct for the patrol points. And then use point.next instead of an array.
    Somehow did not work for me but I think the idea was correct.
     
  18. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Code rewritten by IcemanBo.
    Thumbs up.
     
  19. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,055
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    If you're used to Zinc, it can be handy to write a bit faster, but some things are also missing or worse. :(
    Like static ifs are not allowed outside functions, or it also it seemingly doesn't know the
    readonly
    keyword. But was good experience.:)
     
  20. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    integer ORDER_ID = 851986;

    The 851986 (move) order id seems to make enemy units not to respond to enemy attacks?

    I don't see the point of the
    public struct PatrolPoint { ... }
    struct being public.
    From a user's point of view calling
    patrol_path.add_point(x, y)
    (or equivalent) is better than calling patrol_path.push(PatrolPoint.create(x, y)) because the later introduces artifacts like
    PatrolPathHardDestroy
    .
    PatrolPath
    can expose a
    set_point(point-index, x, y)
    method for changing a point's x and y coordinates.

    Code (vJASS):

    public struct PatrolState 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.
    }
     

    Hm, I think
    PatrolType
    might be a better name for the type of patrolling a unit will be doing.
    Isn't NORMAL a LOOP-ing patrolling, BACKWARD a BACK_AND_FORTH patrolling and LOOP a TELEPORT_LOOP patrolling?

    Code (vJASS):

    public struct PatrolUnit {
        ...
        private timer clock;
        ...
    }
     

    I think you can use a single timer for all patrolling units, this way you won't need the optional TimerUtils library (and not destroy timers when its missing).

    It seems to me that the optional UnitDex library is unnecessary because it only saves a single
    GetUnitTypeId(...)
    call.

    You might want to consider always using curly brackets in the future:
    Code (vJASS):

    static if (!UnitDex)
        deallocate();
        table.integer.remove(GetHandleId(p_u));