1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Trigger Viewer

Patrol System v1.2.1.w3x
Variables
Patrol System
Patrol System
Patrol Config
Patrol State Change
Changelog
Test Map
Init
Create Units
Move Units outside base region
Toggle Script
Attack Mode On
Attack Mode Off
Destroy Script
Enter map-specific custom script code below. This text will be included in the map script after variables are declared and before any trigger code.

		
Name Type Is Array Initial Value
PatrolAttackQTimeout real No 15.00
PatrolAttackTimeout real No 10.00
PatrolBaseAlertedSound sound No SoundNull
PatrolBaseCleanDelay real No 2.50
PatrolBaseGuardSize integer No 3
PatrolBaseLeaveQTimeout real No 15.00
PatrolBaseLeaveTimeout real No 10.00
PatrolBaseOrderDelay real No 5.00
PatrolBaseRallyPoints location Yes
PatrolBaseRegions rect Yes
PatrolBasesCount integer No
PatrolBaseTarget integer No
PatrolCampFacingDelay real No 1.50
PatrolCampFacingPoints location Yes
PatrolCampOrderDelay real No 3.50
PatrolCampRallyPoints location Yes
PatrolCampRegions rect Yes
PatrolCampsCount integer No
PatrolCampSizeMax integer No 3
PatrolCampTargets integer Yes
PatrolChangeColor boolean No false
PatrolEncounterCheckTimeout real No 0.50
PatrolEncounterGroupRange real No 0.00
PatrolEncounterRange real No 0.00
PatrolExcludeClassesCount integer No
PatrolExcludeTypes unitcode Yes
PatrolExcludeTypesCount integer No
PatrolGroupAttackGuardSize integer No 2
PatrolGroupAttackSize integer No 2
PatrolGroupGuardSize integer No 1
PatrolGroupSize integer No 2
PatrolGuardTypes unitcode Yes
PatrolGuardTypesCount integer No
PatrolPlayerDummy player No
PatrolPlayerPatrol player No
PatrolPlayerTarget player No
PatrolRunnersEffect string No war3mapImported\Alert.mdx
PatrolRunnersFirstPingDuration real No 3.00
PatrolRunnersPingDuration real No 1.00
PatrolRunnersPingTimeout real No 2.50
PatrolRunnersSound sound No SoundNull
PatrolRunnersSpeedMultiplier real No 0.75
PatrolScriptAutoDestruct boolean No true
PatrolSystemState real No 0.00
PatrolTargetAttacksSound sound No SoundNull
//===========================================================================
//
//  Patrol System v1.2.1
//  by loktar
//  -------------------------------------------------------------------------
// * Periodically send patrols from Patrol base to camps
// * Camps send patrols to other camps or Patrol base when full
// * Encountered patrols attack Target base and send a Runner to Patrol base
// * New patrols attack Target base instead, when:
// *  * A Runner reaches Patrol base
// *  * Target player attacks Patrol player (excl. patrol units)
//  -------------------------------------------------------------------------
//
//    -------
//    * API *
//    -------
//  *    ConfigPatrolScript()
//          - Configure the script and apply settings
//          -- Script must be toggled off
//          -- If already configured, destroy configured objects
//
//  *    ApplyPatrolSettings()
//          - Apply settings
//          -- Does not include players, regions, points, or targets
//
//  *    TogglePatrolScript(boolean forceDisable)
//          - Enable or disable the script
//          -- Script must be configured to enable
//          -- Enter Base and Enter Camp triggers remain enabled until no units are patrolling
//          --- unless forceDisable == true
//
//  *    TogglePatrolAttack(boolean enable)
//          - Enable or disable Attack Mode
//          -- New patrols attack Target player base instead of patrolling
//          -- Disable or enable Base alerting and Runner pings
//
//  *    DestroyPatrolScript(boolean forceDestroy)
//          - Permanently destroy the script and its objects and give all Dummy player units to Patrol player
//          -- There must be no units patrolling
//          --- unless forceDestroy == true
//          --- otherwise turn on scriptAutoDestruct and disable script
//
//===========================================================================

library PatrolSystem initializer InitPatrol
//===============================================================================
//==== CAMP STRUCT ==============================================================
//===============================================================================
    private struct camp
        group    grp         = CreateGroup()
        group    grpIncoming = CreateGroup() // Units travelling to this camp
        rect     rct
        region   rgn = CreateRegion()
        location loc
        location locRally
        location locFacing
        integer  iTarget // Target camp index (szeCampMax -> target is base)
        trigger  trgLeaveQ = CreateTrigger()
       
        static method create takes rect rct, location locRally, location locFacing, integer iTarget returns camp
            local camp r = camp.allocate()
           
            set r.rct = rct
            set r.loc = Location(GetRectCenterX(r.rct), GetRectCenterY(r.rct))
            set r.locRally = locRally
            set r.locFacing = locFacing
            set r.iTarget = iTarget
            call RegionAddRect(r.rgn, r.rct)
           
            return r
        endmethod
       
        method destroy takes nothing returns nothing
            call QueuedTriggerRemoveBJ(this.trgLeaveQ)
            call DestroyTrigger(this.trgLeaveQ)
            set this.trgLeaveQ = null
           
            call DestroyGroup(this.grp)
            call DestroyGroup(this.grpIncoming)
            set this.grp = null
            set this.grpIncoming = null
           
            set this.rct = null
            call RemoveRegion(this.rgn)
            set this.rgn = null
            call RemoveLocation(this.loc)
            call RemoveLocation(this.locRally)
            call RemoveLocation(this.locFacing)
            set this.loc = null
            set this.locRally = null
            set this.locFacing = null
           
            call camp.deallocate(this)
        endmethod
    endstruct
//===============================================================================
//===============================================================================
   
//===============================================================================
//==== GLOBALS ==================================================================
//===============================================================================  
    globals
        // Script
        private boolean scriptAutoDestruct
        private boolean scriptDestroyed  = false
        private boolean scriptOn         = false
        private boolean disablePending   = false
        private boolean scriptConfigured = false
       
        // Players
        private player  plrTarget // Target player of the patrols
        private player  plrPatrol // Patrolling player
        private player  plrDummy // Dummy player (patrolling units owner)
        private trigger trgPatrolDead
        private trigger trgTargetDead
        private trigger trgDummyDies
       
        // Base & Camps
        private group    grpGetGuardsTmp = null // For GroupGetGuardsEnum()
       
        // Base plrTarget
        private rect     rctTargetBase
        private region   rgnTargetBase
        private location locTargetBase
       
        // Base plrPatrol
        private integer  szeBaseGuard // Minimum amount of guards left in base
        private rect     rctPatrolBase
        private region   rgnPatrolBase
        private location locPatrolBase
        private trigger  trgBaseEnter
       
        // Base Leave
        private integer  iBaseTarget // Target camp index
        private integer  szeGroupGuard // Number of guards leaving base or camps (minimum for base - if available for camps)
        private integer  szeGroup // Includes guards, so should be more than szeGroupGuard
        private real     tmoBaseLeaveQ // tmrBaseLeave timeout
        private real     tmoBaseLeave // tmrBaseLeave timeout when trgBaseLeaveQ is already queued
        private real     dlyBaseOrder // Delay (Sleep) between order to Rally-loc and order to Camp-loc
        private real     dlyBaseClean // Delay (Sleep) between order to Camp-loc and removal from grpBaseLeaving
        private location locBaseRally
        private group    grpBaseLeaving = CreateGroup()
        private timer    tmrBaseLeave   = CreateTimer()
        private trigger  trgBaseLeaveQ  = CreateTrigger()
        private trigger  trgBaseLeave   = CreateTrigger()
       
        // Camps
        private integer    cntCamps = 0
        private integer    szeCampMax
        private real       dlyCampFacing // Delay (Sleep) between order to random point in camp and facing .locFacing
        private real       dlyCampOrder // Delay (Sleep) between order to Rally-loc and order to target-loc
        private trigger    trgCampEnter
        private hashtable  hshCampTargets = InitHashtable()
        private camp array camps
       
        // Patrols
        private boolean        changeColor // Whether to change teamcolor when switching between plrDummy and plrPatrol
        private integer        szeGroupGuardPatrol // szeGroupGuard value when Patrolling
        private integer        szeGroupPatrol // szeGroup value when Patrolling
        private integer        cntGuardTypes     = 0
        private integer        cntExcludeTypes   = 0
        private integer        cntExcludeClasses = 0
        private integer array  idGuardTypes
        private integer array  idExcludeTypes
        private real           tmoPatrolQ // tmoBaseLeaveQ value when Patrolling
        private real           tmoPatrol // tmoBaseLeave value when Patrolling
        private unittype array utpExcludeClasses
        unittype array         pubExcludeClasses // Public for config
       
        // Encounters
        private real    tmoEncounterCheck
        private real    rngEncounter // Range to check for plrTarget units
        private real    rngEncounterGroup // Range to check for plrDummy units
        private group   grpEncounterCheckTmp = null
        private timer   tmrEncounterCheck  = CreateTimer()
        private trigger trgEncounterCheck  = CreateTrigger()
        private trigger trgEncounterCheckQ = CreateTrigger()
        private trigger trgTargetEntersBase
        private trigger trgPlrPatrolAttacked
       
        // Alert
        private constant real ALERT_NONE     = 0
        private constant real ALERT_RUNNER   = 0.01
        private constant real ALERT_ALERTED  = 0.02
        private constant real ALERT_ATTACKED = 0.03
        private sound sndBaseAlerted // Sound to play when Runner reaches base
        private sound sndTargetAttacks // Sound to play when plrTarget attacks plrPatrol or enters base
        private group grpAlerted = CreateGroup()
       
        // Runners
        private real      mplRunnerSpeed // Runner Movement Speed multiplier
        private string    strRunnersFx // Effect to attach to Runner overhead
        private sound     sndRunnersWarning // Sound to play when Runner starts Running
        private group     grpRunners   = CreateGroup()
        private hashtable hshRunnersFx = InitHashtable() // Keeps track of Runners and their Effects
       
        // Runner Ping
        private real    tmoRunnersPing // Runner Movement Speed multiplier
        private real    drnRunnersPing // Runner ping duration
        private real    drnRunnersFirstPing // Runner first ping duration
        private timer   tmrRunnersPing = CreateTimer()
        private trigger trgRunnersPing = CreateTrigger()
       
        // Attack Mode
        private boolean  attackMode = false
        private integer  szeGroupGuardAttack // szeGroupGuard value when Attacking
        private integer  szeGroupAttack // szeGroup value when Attacking
        private real     tmoAttackQ // tmoBaseLeaveQ value when Attacking
        private real     tmoAttack // tmoBaseLeave value when Attacking
    endglobals
//===============================================================================
//===============================================================================
   
//===============================================================================
//==== COMMON ===================================================================
//===============================================================================    
    //==== Set alert state (ALERT_NONE, ALERT_RUNNER, ALERT_ALERTED, ALERT_ATTACKED) ====
    private function SetAlertState takes real alertState returns nothing
        local real state = 0
        if scriptOn then
            set state = 1
        endif
        if attackMode then
            set state = state+0.1
        endif
        set udg_PatrolSystemState = state+alertState
    endfunction
    //===========================================================================
    //===========================================================================

    //==== Reset Runner ====
    private function ResetRunner takes unit runner, boolean removeFromGroup returns nothing
        local integer runnerHId = GetHandleId(runner)
        if removeFromGroup then
            call GroupRemoveUnit(grpRunners, runner)
        endif
        if HaveSavedHandle(hshRunnersFx, runnerHId, 0) then
            call DestroyEffect(LoadEffectHandle(hshRunnersFx, runnerHId, 0))
            call FlushChildHashtable(hshRunnersFx, runnerHId)
        endif
        call SetUnitMoveSpeed(runner, GetUnitDefaultMoveSpeed(runner)) // Runners are slowed down
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== DESTRUCTORS & TOGGLERS UTILITY ===========================================
//===============================================================================  
    //==== Check for patrolling units ====
    private function UnitsArePatrolling takes nothing returns boolean
        local integer iCamp = 0
        if not IsUnitGroupEmptyBJ(grpBaseLeaving) then
            return true
        else
            loop
                exitwhen iCamp >= cntCamps // exit when non-empty group is found
                if not IsUnitGroupEmptyBJ(camps[iCamp].grpIncoming) then
                    return true
                endif
                set iCamp = iCamp+1
            endloop
        endif
       
        return false
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== DESTRUCTORS UTILITY ======================================================
//===============================================================================  
    //==== Give units to plrPatrol ====
    private function SetOwnerPatrolEnum takes nothing returns nothing
        call SetUnitOwner(GetEnumUnit(), plrPatrol, changeColor)
    endfunction
   
    // Reset runners
    private function ResetRunnersEnum takes nothing returns nothing
        call ResetRunner(GetEnumUnit(), false)
    endfunction
    //===========================================================================
    //===========================================================================
   
//===============================================================================
//==== DESTRUCTORS ==============================================================
//===============================================================================    
    //==== Destroy and null configured objects and give all plrDummy units to plrPatrol ====
    private function DestroyConfig takes nothing returns nothing
        local integer i
        local group dummyUnits
       
        set scriptConfigured = false
       
        // Groups
        call ForGroup(grpRunners, function ResetRunnersEnum)
        call DestroyGroup(grpRunners)
        call DestroyGroup(grpBaseLeaving)
        call DestroyGroup(grpAlerted)
        set grpBaseLeaving = null
        set grpAlerted = null
        set grpRunners = null
        if grpGetGuardsTmp != null then
            call DestroyGroup(grpGetGuardsTmp)
            set grpGetGuardsTmp = null
        endif
        if grpEncounterCheckTmp != null then
            call DestroyGroup(grpEncounterCheckTmp)
            set grpEncounterCheckTmp = null
        endif
        call FlushParentHashtable(hshRunnersFx)
        set hshRunnersFx = null

        // Return units to plrPatrol
        set dummyUnits = CreateGroup()
        call GroupEnumUnitsOfPlayer(dummyUnits, plrDummy, null)
        call ForGroup(dummyUnits, function SetOwnerPatrolEnum)
        call DestroyGroup(dummyUnits)
        set dummyUnits = null
       
        // Bases
        call DestroyTrigger(trgBaseEnter)
        call DestroyTrigger(trgTargetEntersBase)
        set trgBaseEnter = null
        set trgTargetEntersBase = null
       
        set rctPatrolBase = null
        set rctTargetBase = null
        call RemoveRegion(rgnPatrolBase)
        call RemoveRegion(rgnTargetBase)
        set rgnPatrolBase = null
        set rgnTargetBase = null
        call RemoveLocation(locPatrolBase)
        call RemoveLocation(locBaseRally)
        call RemoveLocation(locTargetBase)
        set locPatrolBase = null
        set locBaseRally = null
        set locTargetBase = null
       
        // Players
        call DestroyTrigger(trgPlrPatrolAttacked)
        call DestroyTrigger(trgPatrolDead)
        call DestroyTrigger(trgTargetDead)
        call DestroyTrigger(trgDummyDies)
        set trgPlrPatrolAttacked = null
        set trgPatrolDead = null
        set trgTargetDead = null
        set trgDummyDies = null
       
        set plrTarget = null
        set plrPatrol = null
        set plrDummy = null
       
        // Camps
        call DestroyTrigger(trgCampEnter)
        set trgCampEnter = null
       
        set i = 0
        loop
            exitwhen i >= cntCamps
            call camps[i].destroy()
            set i = i+1
        endloop
        call FlushParentHashtable(hshCampTargets)
        set hshCampTargets = null
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== Destroy and null all objects and give all plrDummy units to plrPatrol ====
    function DestroyPatrolScript takes boolean forceDestroy returns nothing
        //Only proceed if all patrols are idle or alerted
        if not scriptDestroyed and (forceDestroy or not UnitsArePatrolling()) then
            set scriptDestroyed = true
            set scriptOn = false
            set udg_PatrolSystemState = -1
           
            // Triggers
            call QueuedTriggerRemoveBJ(trgBaseLeaveQ)
            call QueuedTriggerRemoveBJ(trgEncounterCheckQ)
            call DestroyTrigger(trgBaseLeave)
            call DestroyTrigger(trgBaseLeaveQ)
            call DestroyTrigger(trgEncounterCheck)
            call DestroyTrigger(trgEncounterCheckQ)
            call DestroyTrigger(trgRunnersPing)
            call DestroyTrigger(trgDummyDies)
            set trgBaseLeave = null
            set trgBaseLeaveQ = null
            set trgEncounterCheck = null
            set trgEncounterCheckQ = null
            set trgRunnersPing = null
            set trgDummyDies = null
           
            // Timers
            call DestroyTimer(tmrBaseLeave)
            call DestroyTimer(tmrEncounterCheck)
            call DestroyTimer(tmrRunnersPing)
            set tmrBaseLeave = null
            set tmrEncounterCheck = null
            set tmrRunnersPing = null
           
            // Sounds
            set sndBaseAlerted = null
            set sndTargetAttacks = null
            set sndRunnersWarning = null
           
            // Destroy configured objects
            call DestroyConfig()
        else
            set scriptAutoDestruct = true
            if scriptOn then
                call TogglePatrolScript.evaluate(false)
            endif
        endif
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================
   
//===============================================================================
//==== TOGGLERS =================================================================
//===============================================================================
    //==== Disable/enable attack mode ====
    function TogglePatrolAttack takes boolean enable returns nothing
        if not scriptDestroyed then
            if enable then
                if attackMode != enable then
                    set udg_PatrolSystemState = udg_PatrolSystemState+0.1
                    set attackMode = true
                    set szeGroupGuard = szeGroupGuardAttack
                    set szeGroup = szeGroupAttack
                    set tmoBaseLeaveQ = tmoAttackQ
                    set tmoBaseLeave = tmoAttack
                endif
               
                if scriptOn then
                    call DisableTrigger(trgTargetEntersBase)
                    call DisableTrigger(trgPlrPatrolAttacked)
                    call DisableTrigger(trgRunnersPing)
                endif
            else
                if attackMode != enable then
                    set udg_PatrolSystemState = udg_PatrolSystemState-0.1
                    set attackMode = false
                    set szeGroupGuard = szeGroupGuardPatrol
                    set szeGroup = szeGroupPatrol
                    set tmoBaseLeaveQ = tmoPatrolQ
                    set tmoBaseLeave = tmoPatrol
                   
                    if IsUnitGroupEmptyBJ(grpRunners) then
                        call SetAlertState(ALERT_NONE)
                    else
                        call SetAlertState(ALERT_RUNNER)
                    endif
                endif
               
                if scriptOn then
                    call EnableTrigger(trgTargetEntersBase)
                    call EnableTrigger(trgPlrPatrolAttacked)
                    call EnableTrigger(trgRunnersPing)
                   
                    call TimerStart(tmrRunnersPing, tmoRunnersPing, false, null)
                endif
            endif
        endif
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== Disable Enter base/camps triggers ====
    private function DisablePendingTriggers takes boolean forceDisable returns nothing
        if not scriptOn then
            if forceDisable or not UnitsArePatrolling() then
                call DisableTrigger(trgBaseEnter)
                call DisableTrigger(trgCampEnter)
                call DisableTrigger(trgPatrolDead)
                call DisableTrigger(trgTargetDead)
                call DisableTrigger(trgDummyDies)
                set disablePending = false
                if scriptAutoDestruct then
                    call DestroyPatrolScript(false)
                endif
            else
                set disablePending = true
            endif
        else
            set disablePending = false
        endif
    endfunction
    //========
   
    //==== Disable/enable script and start timers ====
    function TogglePatrolScript takes boolean forceDisable returns nothing
        local integer iCamp = 0
       
        if not scriptDestroyed then
            if scriptOn then // TURN OFF PATROL SCRIPT
                set scriptOn = false
                set udg_PatrolSystemState = udg_PatrolSystemState-1
               
                call QueuedTriggerRemoveBJ(trgBaseLeaveQ)
                call QueuedTriggerRemoveBJ(trgEncounterCheckQ)
                call DisableTrigger(trgBaseLeave)
                call DisableTrigger(trgBaseLeaveQ)
                call DisableTrigger(trgEncounterCheckQ)
                call DisableTrigger(trgEncounterCheck)
                call DisableTrigger(trgTargetEntersBase)
                call DisableTrigger(trgPlrPatrolAttacked)
               
                loop
                    exitwhen iCamp >= cntCamps
                    call QueuedTriggerRemoveBJ(camps[iCamp].trgLeaveQ)
                    call DisableTrigger(camps[iCamp].trgLeaveQ)
                    set iCamp = iCamp+1
                endloop
               
                call DisablePendingTriggers(forceDisable)
            else // TURN ON PATROL SCRIPT
                set scriptOn = true
                set udg_PatrolSystemState = udg_PatrolSystemState+1
               
                if not disablePending then
                    call EnableTrigger(trgBaseEnter)
                    call EnableTrigger(trgCampEnter)
                    call EnableTrigger(trgPatrolDead)
                    call EnableTrigger(trgTargetDead)
                    call EnableTrigger(trgDummyDies)
                else
                    set disablePending = false
                endif
               
                call EnableTrigger(trgBaseLeaveQ)
                call EnableTrigger(trgBaseLeave)
                call EnableTrigger(trgEncounterCheckQ)
                call EnableTrigger(trgEncounterCheck)
                call EnableTrigger(trgTargetEntersBase)
                call EnableTrigger(trgPlrPatrolAttacked)
               
                loop
                    exitwhen iCamp >= cntCamps
                    call EnableTrigger(camps[iCamp].trgLeaveQ)
                    set iCamp = iCamp+1
                endloop
               
                //Start sending patrols
                call TimerStart(tmrBaseLeave, tmoBaseLeaveQ, false, null)
                call TimerStart(tmrEncounterCheck, tmoEncounterCheck, false, null)
            endif
           
            call TogglePatrolAttack(attackMode)
        endif
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== BASE & CAMP UTILITY ======================================================
//===============================================================================
    //==== Guard typeid check ====
    private function IdIsGuardType takes integer utype returns boolean
        local integer iGuardType = 0
        loop
            exitwhen iGuardType >= cntGuardTypes
            if utype == idGuardTypes[iGuardType] then
                return true
            endif
            set iGuardType = iGuardType+1
        endloop
        return false
    endfunction
    //========

    //==== Get group guards ====
    private function GroupGetGuardsEnum takes nothing returns nothing
        local unit enumUnit = GetEnumUnit()
        if GetUnitState(enumUnit, UNIT_STATE_LIFE) > 0 and IdIsGuardType(GetUnitTypeId(enumUnit)) then
            call GroupAddUnit(grpGetGuardsTmp, enumUnit)
        endif
        set enumUnit = null
    endfunction
    //========
   
    //==== Pick Patrol group from group ====
    private function GroupPickPatrolGroup takes group patrolGroup, boolean isBase returns group
        local group guardGroup = null
        local group pickedGroup = null
        local unit pickedUnit = null
        local integer guardCount
        local integer patrolCount = CountUnitsInGroup(patrolGroup)
        local integer pickedCount = 0
        local boolean isGuard
        local boolean keepPicked
       
        if (not isBase and patrolCount > szeCampMax) or (isBase and patrolCount >= szeGroup) then
            // Get Guards
            set grpGetGuardsTmp = CreateGroup()
            call ForGroup(patrolGroup, function GroupGetGuardsEnum)
            set guardGroup = grpGetGuardsTmp
            set grpGetGuardsTmp = null
            set guardCount = CountUnitsInGroup(guardGroup)
           
            if not isBase or guardCount >= szeGroupGuard+szeBaseGuard then
                set pickedGroup = CreateGroup()
               
                loop
                    exitwhen pickedCount >= szeGroup or patrolCount <= 0
                   
                    // Pick Guards
                    if pickedCount < szeGroupGuard and guardCount > 0 then
                        set pickedUnit = GroupPickRandomUnit(guardGroup)
                        set isGuard = true
                        set keepPicked = true
                    // Pick Rest
                    else
                        set pickedUnit = GroupPickRandomUnit(patrolGroup)
                        set isGuard = IdIsGuardType(GetUnitTypeId(pickedUnit))
                        set keepPicked = not isGuard or patrolCount-guardCount <= 0 // Prefer non-guards
                    endif
                   
                    if keepPicked then
                        if isGuard then
                            call GroupRemoveUnit(guardGroup, pickedUnit)
                            set guardCount = guardCount-1
                        endif
                       
                        call GroupRemoveUnit(patrolGroup, pickedUnit)
                        call GroupAddUnit(pickedGroup, pickedUnit)
                        set patrolCount = patrolCount-1
                        set pickedCount = pickedCount+1
                       
                        if isBase then
                            call GroupAddUnit(grpBaseLeaving, pickedUnit)
                            call SetUnitOwner(pickedUnit, plrDummy, changeColor)
                            call RemoveGuardPosition(pickedUnit)
                        endif
                    endif
                endloop
                   
                set pickedUnit = null
            endif
       
            call DestroyGroup(guardGroup)
            set guardGroup = null
        endif
       
        // Swap groups so local agent can be nulled
        set patrolGroup = pickedGroup
        set pickedGroup = null
       
        return patrolGroup
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== Increment Target Index ====
    private function TargetIndexIncr takes integer iTarget, integer exclude returns integer
        loop
            set iTarget = iTarget+1
            if iTarget > cntCamps then
                set iTarget = 0
            endif
           
            exitwhen iTarget != exclude and iTarget <= cntCamps
        endloop
       
        return iTarget
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== BASE UTILITY =============================================================
//===============================================================================
    //==== plrDummy not in grpBaseLeaving or grpAlerted and no orders filter ====
    private function IdlePatrolInBaseFltr takes nothing returns boolean
        local unit filterUnit = GetFilterUnit()
        local boolean r = GetUnitState(filterUnit, UNIT_STATE_LIFE) > 0 and GetOwningPlayer(filterUnit) == plrDummy and not IsUnitInGroup(filterUnit, grpAlerted)
        set r = r and not IsUnitInGroup(filterUnit, grpBaseLeaving) and GetUnitCurrentOrder(filterUnit) != 851983 // = "attack"
        set filterUnit = null
        return r
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== Patrol type filter ====
    private function PatrolTypeFltr takes nothing returns boolean
        local unit filterUnit = GetFilterUnit()
        local integer filterId
        local integer iExclude
       
        // Unit alive & Player check
        if GetUnitState(filterUnit, UNIT_STATE_LIFE) <= 0 and GetOwningPlayer(filterUnit) != plrPatrol then
            return false
        else
            // Type check
            set filterId = GetUnitTypeId(filterUnit)
            set iExclude = 0
            loop
                exitwhen iExclude >= cntExcludeTypes
                if filterId == idExcludeTypes[iExclude] then
                    return false
                endif
                set iExclude = iExclude+1
            endloop
           
            // Classification check
            set iExclude = 0
            loop
                exitwhen iExclude >= cntExcludeClasses
                if IsUnitType(filterUnit, utpExcludeClasses[iExclude]) then
                    return false
                endif
                set iExclude = iExclude+1
            endloop
        endif
       
        set filterUnit = null
       
        return true
    endfunction
    //===========================================================================
    //===========================================================================
   
//===============================================================================
//==== BASE ACTIONS =============================================================
//===============================================================================
    //==== Give orders to idle patrols in base ====
    private function IdlePatrolInBaseEnum takes nothing returns nothing
        local unit enumUnit = GetEnumUnit()
        local integer iCamp = 0
       
        loop // Find unit's target camp
            exitwhen iCamp >= cntCamps or IsUnitInGroup(enumUnit, camps[iCamp].grpIncoming)
            set iCamp = iCamp+1
        endloop
       
        if iCamp < cntCamps then
            call IssuePointOrderLoc(enumUnit, "attack", camps[iCamp].loc)
        elseif attackMode then
            call IssuePointOrderLoc(enumUnit, "attack", locTargetBase)
        else // Recycle unit
            call SetUnitOwner(enumUnit, plrPatrol, changeColor)
            call RecycleGuardPosition(enumUnit)
        endif
    endfunction
    //========
   
    //==== Send patrols from base ====
    private function BaseLeaveQAcn takes nothing returns nothing
        local group patrolGroup = null
        local group leavingGroup = null
       
        if scriptOn then
            // Get leaving group
            set patrolGroup = CreateGroup()
            call GroupEnumUnitsInRect(patrolGroup, rctPatrolBase, function PatrolTypeFltr)
            set leavingGroup = GroupPickPatrolGroup(patrolGroup, true)
            call DestroyGroup(patrolGroup)
            set patrolGroup = null
           
            if leavingGroup != null and not IsUnitGroupEmptyBJ(leavingGroup) then
                // Send patrols
                call GroupPointOrderLoc(leavingGroup, "attack", locBaseRally)
                call TriggerSleepAction(dlyBaseOrder)
               
                if not attackMode then
                    call GroupPointOrderLoc(leavingGroup, "attack", camps[iBaseTarget].loc)
                    call GroupAddGroup(leavingGroup, camps[iBaseTarget].grpIncoming)
                    set iBaseTarget = TargetIndexIncr(iBaseTarget, cntCamps)
                else
                    call GroupPointOrderLoc(leavingGroup, "attack", locTargetBase)
                endif
               
                // Find idle patrols and correct
                set patrolGroup = CreateGroup()
                call GroupEnumUnitsInRect(patrolGroup, rctPatrolBase, function IdlePatrolInBaseFltr)
                if not IsUnitGroupEmptyBJ(patrolGroup) then
                    call ForGroup(patrolGroup, function IdlePatrolInBaseEnum)
                endif
                call DestroyGroup(patrolGroup)
                set patrolGroup = null
               
                call QueuedTriggerRemoveBJ(trgBaseLeaveQ)
               
                // Remove from leaving group after delay
                call TriggerSleepAction(dlyBaseClean)
                call GroupRemoveGroup(leavingGroup, grpBaseLeaving)
            else
                call QueuedTriggerRemoveBJ(trgBaseLeaveQ)
            endif
           
            if leavingGroup != null then
                call DestroyGroup(leavingGroup)
                set leavingGroup = null
            endif
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
    endfunction
    //========
   
    //==== tmrBaseLeave expired - queue trigger and/or start timer ====
    private function BaseLeaveAcn takes nothing returns nothing
        if scriptOn then
            // Limit to one in queue
            if QueuedTriggerGetIndex(trgBaseLeaveQ) == -1 then
                call QueuedTriggerAddBJ(trgBaseLeaveQ, true)
                call TimerStart(tmrBaseLeave, tmoBaseLeaveQ, false, null)
            else
                call TimerStart(tmrBaseLeave, tmoBaseLeave, false, null)
            endif
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
    endfunction
    //===========================================================================
    //===========================================================================

    //==== plrDummy unit (not in grpBaseLeaving) enters base - recycle unit ====
    private function BaseEnterFltrAcn takes nothing returns boolean
        local unit enteringUnit = GetFilterUnit()

        if GetOwningPlayer(enteringUnit) == plrDummy and not IsUnitInGroup(enteringUnit, grpBaseLeaving) then
            if scriptOn or disablePending then
                call SetUnitOwner(enteringUnit, plrPatrol, changeColor)
                call RecycleGuardPosition(enteringUnit)
               
                if IsUnitInGroup(enteringUnit, grpAlerted) then
                    call GroupRemoveUnit(grpAlerted, enteringUnit)
                   
                    if not attackMode then
                        call SetAlertState(ALERT_ALERTED)
                        call TogglePatrolAttack(true)
                       
                        if sndBaseAlerted != null and not GetSoundIsPlaying(sndBaseAlerted) then
                            call StartSound(sndBaseAlerted)
                        endif
                    endif
                   
                    if IsUnitInGroup(enteringUnit, grpRunners) then
                        call ResetRunner(enteringUnit, true)
                    endif
                endif
           
                if disablePending then
                    call DisablePendingTriggers(false)
                endif
            elseif scriptAutoDestruct then
                call DestroyPatrolScript(false)
            endif
        endif
               
        set enteringUnit = null
        return false
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================
   
//===============================================================================
//==== CAMP ACTIONS =============================================================
//===============================================================================
    //==== Queue sending out patrols ====
    private function QCampsLeave takes integer iCamp returns nothing
        if CountUnitsInGroup(camps[iCamp].grp) > szeCampMax and QueuedTriggerGetIndex(camps[iCamp].trgLeaveQ) == -1 then
            call QueuedTriggerAddBJ(camps[iCamp].trgLeaveQ, true)
        endif
    endfunction

    //==== Order units to next camp ====
    private function CampsLeaveQAcn takes nothing returns nothing
        local integer iCamp
        local group leavingGroup = null
       
        if scriptOn then
            set iCamp = LoadInteger(hshCampTargets, GetHandleId(GetTriggeringTrigger()), 0)
            set leavingGroup = GroupPickPatrolGroup(camps[iCamp].grp, false)
           
            if leavingGroup != null and not IsUnitGroupEmptyBJ(leavingGroup) then
                // Give orders
                call GroupPointOrderLoc(leavingGroup, "attack", camps[iCamp].locRally)
                call TriggerSleepAction(dlyCampOrder)
               
                if camps[iCamp].iTarget == cntCamps then
                    call GroupPointOrderLoc(leavingGroup, "attack", locPatrolBase)
                else
                    call GroupPointOrderLoc(leavingGroup, "attack", camps[camps[iCamp].iTarget].loc)
                    call GroupAddGroup(leavingGroup, camps[camps[iCamp].iTarget].grpIncoming)
                endif
               
                // Increment target index
                set camps[iCamp].iTarget = TargetIndexIncr(camps[iCamp].iTarget, iCamp)
            endif
           
            if leavingGroup != null then
                call DestroyGroup(leavingGroup)
                set leavingGroup = null
            endif
           
            call QueuedTriggerRemoveBJ(camps[iCamp].trgLeaveQ)
            call QCampsLeave(iCamp)
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
    endfunction
    //===========================================================================
    //===========================================================================

    //==== plrDummy Unit (not in grpAlert) Enters Camp - handle arriving unit and queue sending next patrols ====
    private function CampEnterAcn takes nothing returns nothing
        local integer iCamp
        local unit enteringUnit = null
        local real randomX
        local real randomY
       
        if scriptOn or disablePending then
            set enteringUnit = GetEnteringUnit()
            set iCamp = LoadInteger(hshCampTargets, GetHandleId(GetTriggeringRegion()), 0)
           
            if iCamp < cntCamps and IsUnitInGroup(enteringUnit, camps[iCamp].grpIncoming) then
                call GroupRemoveUnit(camps[iCamp].grpIncoming, enteringUnit)
               
                set randomX = GetRandomReal(GetRectMinX(camps[iCamp].rct), GetRectMaxX(camps[iCamp].rct))
                set randomY = GetRandomReal(GetRectMinY(camps[iCamp].rct), GetRectMaxY(camps[iCamp].rct))
                call IssuePointOrder(enteringUnit, "attack", randomX, randomY)
                call TriggerSleepAction(dlyCampFacing)
                call SetUnitFacingToFaceLocTimed(enteringUnit, camps[iCamp].locFacing, 0.50)
               
                call GroupAddUnit(camps[iCamp].grp, enteringUnit)
               
                call QCampsLeave(iCamp)
            endif
           
            set enteringUnit = null
       
            if disablePending then
                call DisablePendingTriggers(false)
            endif
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== ENCOUNTER & PLAYER UTILITY ===============================================
//===============================================================================
    //==== plrTarget, Alive and Visible check ====
    private function IsTargetEncounter takes unit checkUnit returns boolean
        return GetOwningPlayer(checkUnit) == plrTarget and GetUnitState(checkUnit, UNIT_STATE_LIFE) > 0 and IsUnitVisible(checkUnit, plrDummy)
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== ENCOUNTER UTILITY ========================================================
//===============================================================================
    //==== plrTarget, Alive and Visible filter ====
    private function TargetEncounterFltr takes nothing returns boolean
        return IsTargetEncounter(GetFilterUnit())
    endfunction
    //========
   
    //==== Check not grpAlerted & Alive ====
    private function NoAlertCheck takes unit checkUnit returns boolean
        return GetUnitState(checkUnit, UNIT_STATE_LIFE) > 0 and not IsUnitInGroup(checkUnit, grpAlerted)
    endfunction
   
    //==== not grpAlerted filter ====
    private function NoAlertFltr takes nothing returns boolean
        return NoAlertCheck(GetFilterUnit())
    endfunction
   
    //==== plrDummy and not grpAlerted filter ====
    private function OwnerDummyNoAlertFltr takes nothing returns boolean
        local unit filterUnit = GetFilterUnit()
        local boolean r = GetOwningPlayer(filterUnit) == plrDummy and NoAlertCheck(filterUnit)
        set filterUnit = null
        return r
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== Remove unit from all groups ====
    private function UnitRemoveFromGroups takes unit removeUnit, boolean exceptAlert returns nothing
        local integer iCamp = 0
        call GroupRemoveUnit(grpBaseLeaving, removeUnit)
        if not exceptAlert then
            call GroupRemoveUnit(grpAlerted, removeUnit)
            call ResetRunner(removeUnit, true)
            if grpEncounterCheckTmp != null then
                call GroupRemoveUnit(grpEncounterCheckTmp, removeUnit)
            endif
        endif
       
        loop
            exitwhen iCamp >= cntCamps
            call GroupRemoveUnit(camps[iCamp].grp, removeUnit)
            call GroupRemoveUnit(camps[iCamp].grpIncoming, removeUnit)
            set iCamp = iCamp+1
        endloop
    endfunction
    //========
   
    //==== Remove group from all groups ====
    private function GroupRemoveFromGroupsEnum takes nothing returns nothing
        call UnitRemoveFromGroups(GetEnumUnit(), false)
    endfunction
    //===========================================================================
    //===========================================================================
   
//===============================================================================
//==== ENCOUNTER ACTIONS ========================================================
//===============================================================================
    //==== Handle Encounters ====
    private function EncounterCheckEnum takes nothing returns nothing
        local unit alertUnit = null
        local group alertGroup = null
        local real unitX
        local real unitY
        local real range
       
        if scriptOn then
            set alertUnit = GetEnumUnit()
           
            if not IsUnitInGroup(alertUnit, grpAlerted) then
                set unitX = GetUnitX(alertUnit)
                set unitY = GetUnitY(alertUnit)
               
                // Get plrTarget units in range
                if rngEncounter > 0 then
                    set range = rngEncounter
                else
                    set range = GetUnitAcquireRange(alertUnit)
                endif
               
                set alertGroup = CreateGroup()
                call GroupEnumUnitsInRangeCounted(alertGroup, unitX, unitY, range, function TargetEncounterFltr, 1)
               
                if not IsUnitGroupEmptyBJ(alertGroup) then
                    call DestroyGroup(alertGroup)
                   
                    // Get plrDummy units in range
                    if rngEncounterGroup > 0 then
                        set range = rngEncounterGroup
                    elseif range == rngEncounter then
                        set range = GetUnitAcquireRange(alertUnit)
                    endif
                   
                    set alertGroup = CreateGroup()
                    call GroupEnumUnitsInRange(alertGroup, unitX, unitY, range, function OwnerDummyNoAlertFltr)
                    call GroupAddUnit(alertGroup, alertUnit)
                    call ForGroup(alertGroup, function GroupRemoveFromGroupsEnum)
                    call GroupAddGroup(alertGroup, grpAlerted)
                   
                    // Attack target base
                    call GroupPointOrderLoc(alertGroup, "attack", locTargetBase)
                   
                    // Pick Runner
                    if not attackMode then
                        set alertUnit = GroupPickRandomUnit(alertGroup)
                        call GroupAddUnit(grpRunners, alertUnit)
                        call SetAlertState(ALERT_RUNNER)
                   
                        if strRunnersFx != null and strRunnersFx != "" then
                            call SaveEffectHandle(hshRunnersFx, GetHandleId(alertUnit), 0, AddSpecialEffectTarget(strRunnersFx, alertUnit, "overhead"))
                        endif
                       
                        if sndRunnersWarning != null and not GetSoundIsPlaying(sndRunnersWarning) then
                            call StartSound(sndRunnersWarning)
                        endif
                       
                        call SetUnitMoveSpeed(alertUnit, GetUnitDefaultMoveSpeed(alertUnit)*mplRunnerSpeed)
                        call IssuePointOrderLoc(alertUnit, "move", locPatrolBase)
                        if drnRunnersFirstPing > 0 and GetLocalPlayer() == plrTarget then
                            set unitX = GetUnitX(alertUnit)
                            set unitY = GetUnitY(alertUnit)
                            call PingMinimap(unitX, unitY, drnRunnersFirstPing)
                            call SetCameraQuickPosition(unitX, unitY)
                        endif
                    endif
                endif
               
                call DestroyGroup(alertGroup)
                set alertGroup = null
            endif
           
            set alertUnit = null
       
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
    endfunction
    //========
   
    //==== Remove alerted patrols from other groups and make sure they have their orders ====
    private function AlertGroupCheckEnum takes nothing returns nothing
        local unit enumUnit = GetEnumUnit()
       
        // Make sure alerted patrols are only in grpAlerted and grpRunners
        call UnitRemoveFromGroups(enumUnit, true)
       
        // Make sure alerted patrols are doing their job
        if IsUnitInGroup(enumUnit, grpRunners) then
            if GetUnitCurrentOrder(enumUnit) != 851986 then // == move
                call IssuePointOrderLoc(enumUnit, "move", locPatrolBase)
            endif
        else
            if GetUnitCurrentOrder(enumUnit) != 851983 then // == attack
                call IssuePointOrderLoc(enumUnit, "attack", locTargetBase)
            endif
        endif
       
        set enumUnit = null
    endfunction
    //========
   
    //==== Check for Encounters  ====
    private function EncounterCheckQCndAcn takes nothing returns boolean
        if scriptOn then
            set grpEncounterCheckTmp = CreateGroup() // enumUnits are removed from this group in EncounterCheckEnum
            call GroupEnumUnitsOfPlayer(grpEncounterCheckTmp, plrDummy, function NoAlertFltr)
            call ForGroup(grpEncounterCheckTmp, function EncounterCheckEnum)
            call DestroyGroup(grpEncounterCheckTmp)
            set grpEncounterCheckTmp = null
           
            call ForGroup(grpAlerted, function AlertGroupCheckEnum)
           
            call QueuedTriggerRemoveBJ(trgEncounterCheckQ)
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return false
    endfunction
    //========
   
    //==== tmrEncounterCheck expires - queue Check  ====
    private function EncounterCheckCndAcn takes nothing returns boolean
        if scriptOn then
            if QueuedTriggerGetIndex(trgEncounterCheckQ) == -1 then
                call QueuedTriggerAddBJ(trgEncounterCheckQ, true)
            endif
           
            call TimerStart(tmrEncounterCheck, tmoEncounterCheck, false, null)
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return false
    endfunction
    //===========================================================================
    //===========================================================================
   
    ///==== Ping Runners ====
    private function RunnersPingEnum takes nothing returns nothing
        local unit enumUnit = null
        local real enumX
        local real enumY
       
        if drnRunnersPing > 0 and GetLocalPlayer() == plrTarget then
            set enumUnit = GetEnumUnit()
            set enumX = GetUnitX(enumUnit)
            set enumY = GetUnitY(enumUnit)
            call PingMinimap(enumX, enumY, drnRunnersPing)
            call SetCameraQuickPosition(enumX, enumY)
           
            set enumUnit = null
        endif
    endfunction
    //========
   
    //==== tmrRunnersPing expires - ping runners  ====
    private function RunnersPingCndAcn takes nothing returns boolean
        if scriptOn then
            if not attackMode then
                call ForGroup(grpRunners, function RunnersPingEnum)
                call TimerStart(tmrRunnersPing, tmoRunnersPing, false, null)
            endif
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return true
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== plrPatrol is attacked or plrTarget enters plrPatrol base ====
    private function TargetAggroFltrAcn takes nothing returns boolean
        if scriptOn then
            if not attackMode and IsTargetEncounter(GetFilterUnit()) then
                call SetAlertState(ALERT_ATTACKED)
                call TogglePatrolAttack(true)
               
                if sndTargetAttacks != null and not GetSoundIsPlaying(sndTargetAttacks) then
                    call StartSound(sndTargetAttacks)
                endif
            endif
       
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return false
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== PLAYER UTILITY ===========================================================
//===============================================================================
    //==== plrPatrol filter ====
    private function OwnerPatrolOrDummyFltr takes nothing returns boolean
        local unit filterUnit = GetFilterUnit()
        local boolean r = GetUnitState(filterUnit, UNIT_STATE_LIFE) > 0 and (GetOwningPlayer(filterUnit) == plrPatrol or GetOwningPlayer(filterUnit) == plrDummy)
        set filterUnit = null
        return r
    endfunction
    //========
   
    //==== plrTarget filter ====
    private function OwnerTargetFltr takes nothing returns boolean
        local unit filterUnit = GetFilterUnit()
        local boolean r = GetUnitState(filterUnit, UNIT_STATE_LIFE) > 0 and GetOwningPlayer(filterUnit) == plrTarget
        set filterUnit = null
        return r
    endfunction
    //===========================================================================
    //===========================================================================
   
//===============================================================================
//==== PLAYER ACTIONS ===========================================================
//===============================================================================
    //==== plrPatrol or plrDummy unit dies - stop script if base empty and no units patrolling ====
    private function PatrolBaseDeadFltrAcn takes nothing returns boolean
        local group patrolBaseUnits = null
        if scriptOn then
            call TriggerSleepAction(0.1) // Make sure trgDummyDies executes first (removal from groups)
           
            if not UnitsArePatrolling() then
                set patrolBaseUnits = CreateGroup()
                call GroupEnumUnitsInRectCounted(patrolBaseUnits, rctPatrolBase, function OwnerPatrolOrDummyFltr, 1)
               
                // No living plrPatrol or plrDummy units in base -> stop the script
                if IsUnitGroupEmptyBJ(patrolBaseUnits) then
                    call TogglePatrolScript(false)
                endif
               
                call DestroyGroup(patrolBaseUnits)
                set patrolBaseUnits = null
            endif
       
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return false
    endfunction
    //========
   
    //==== plrTarget unit dies or patrol enters Target base - stop attacks if base empty ====
    private function TargetBaseDeadFltrAcn takes nothing returns boolean
        local group targetBaseUnits = null
        local unit filterUnit = null
        if scriptOn then
            set filterUnit = GetFilterUnit() // Killing or entering unit
           
            // Only if unit was killed in base by plrDummy or plrPatrol
            if IsUnitInRegion(rgnTargetBase, filterUnit) and (GetOwningPlayer(filterUnit) == plrDummy or GetOwningPlayer(filterUnit) == plrPatrol) then
                set targetBaseUnits = CreateGroup()
                call GroupEnumUnitsInRectCounted(targetBaseUnits, rctTargetBase, function OwnerTargetFltr, 1)
               
                // No living plrTarget units in base -> stop attacks
                if IsUnitGroupEmptyBJ(targetBaseUnits) then
                    call TogglePatrolAttack(false)
                endif
               
                call DestroyGroup(targetBaseUnits)
                set targetBaseUnits = null
            endif
           
            set filterUnit = null
       
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return false
    endfunction
    //===========================================================================
    //===========================================================================
   
    //==== plrDummy unit dies  ====
    private function DummyDiesCndAcn takes nothing returns boolean
        local unit dyingUnit = null

        if scriptOn then
            set dyingUnit = GetDyingUnit()
           
            if not attackMode and IsUnitInGroup(dyingUnit, grpRunners) and CountUnitsInGroup(grpRunners) <= 1 then
                call SetAlertState(ALERT_NONE)
            endif
           
            call UnitRemoveFromGroups(dyingUnit, false)
            call SetUnitOwner(dyingUnit, plrPatrol, false)
           
            set dyingUnit = null
           
        elseif disablePending then
            call DisablePendingTriggers(false)
        elseif scriptAutoDestruct then
            call DestroyPatrolScript(false)
        endif
       
        return false
    endfunction
    //===========================================================================
    //===========================================================================
//===============================================================================
//===============================================================================

//===============================================================================
//==== CONFIGURE UTILITY ========================================================
//===============================================================================
    //==== Is player start loc in rect? ====
    private function RectContainsPlayerStart takes rect r, player p returns boolean
        local integer start = GetPlayerStartLocation(p)
        local real x = GetStartLocationX(start)
        local real y = GetStartLocationY(start)
       
        return GetRectMinX(r) <= x and x <= GetRectMaxX(r) and GetRectMinY(r) <= y and y <= GetRectMaxY(r)
    endfunction
    //===========================================================================
    //===========================================================================
   
//===============================================================================
//==== CONFIGURE ================================================================
//===============================================================================
    function ApplyPatrolSettings takes nothing returns nothing
        local integer i
       
        if not scriptDestroyed then            
            set scriptAutoDestruct = udg_PatrolScriptAutoDestruct
           
            // Patrol Settings
            set changeColor = udg_PatrolChangeColor
            set szeGroupPatrol = udg_PatrolGroupSize
            set szeGroupAttack = udg_PatrolGroupAttackSize
            set szeGroup = szeGroupPatrol
            set szeGroupGuardPatrol = udg_PatrolGroupGuardSize
            set szeGroupGuardAttack = udg_PatrolGroupAttackGuardSize
            set szeGroupGuard = szeGroupGuardPatrol
           
            // Encounter settings
            set tmoEncounterCheck = udg_PatrolEncounterCheckTimeout
            set rngEncounter = udg_PatrolEncounterRange
            set rngEncounterGroup = udg_PatrolEncounterGroupRange
           
            // Runner settings
            set mplRunnerSpeed = udg_PatrolRunnersSpeedMultiplier
            set tmoRunnersPing = udg_PatrolRunnersPingTimeout
            set drnRunnersPing = udg_PatrolRunnersPingDuration
            set drnRunnersFirstPing = udg_PatrolRunnersFirstPingDuration
            set strRunnersFx = udg_PatrolRunnersEffect
           
            // Base settings
            set szeBaseGuard = udg_PatrolBaseGuardSize
            set tmoPatrolQ = udg_PatrolBaseLeaveQTimeout
            set tmoAttackQ = udg_PatrolAttackQTimeout
            set tmoBaseLeaveQ = tmoPatrolQ
            set tmoPatrol = udg_PatrolBaseLeaveTimeout
            set tmoAttack = udg_PatrolAttackTimeout
            set tmoBaseLeave = tmoPatrol
            set dlyBaseOrder = udg_PatrolBaseOrderDelay
            set dlyBaseClean = udg_PatrolBaseCleanDelay
           
            // Camp settings
            set dlyCampOrder = udg_PatrolCampOrderDelay
            set dlyCampFacing = udg_PatrolCampFacingDelay
            set szeCampMax = udg_PatrolCampSizeMax
           
            // Sounds
            set sndRunnersWarning = udg_PatrolRunnersSound
            set sndBaseAlerted = udg_PatrolBaseAlertedSound
            set sndTargetAttacks = udg_PatrolTargetAttacksSound
           
            // Guard unit types
            set cntGuardTypes = udg_PatrolGuardTypesCount
            set i = 0
            loop
                exitwhen i >=  cntGuardTypes
                set idGuardTypes[i] = udg_PatrolGuardTypes[i]
                set i = i+1
            endloop
           
            // Excluded unit types
            set cntExcludeTypes = udg_PatrolExcludeTypesCount
            set i = 0
            loop
                exitwhen i >= cntExcludeTypes
                set idExcludeTypes[i] = udg_PatrolExcludeTypes[i]
                set i = i+1
            endloop
           
            // Excluded classifications
            set cntExcludeClasses = udg_PatrolExcludeClassesCount
            set i = 0
            loop
                exitwhen i >= cntExcludeClasses
                set utpExcludeClasses[i] = pubExcludeClasses[i]
                set i = i+1
            endloop
        endif
    endfunction
   
    function ConfigPatrolScript takes nothing returns nothing
        local integer i
        local boolean rectAssigned
       
        if not scriptDestroyed and not scriptOn and not disablePending then
            if scriptConfigured then
                call DestroyConfig()
            else
                set scriptConfigured = true
            endif
           
            call ApplyPatrolSettings()
       
            // Players
            set plrPatrol = udg_PatrolPlayerPatrol
            set plrTarget = udg_PatrolPlayerTarget
            set plrDummy = udg_PatrolPlayerDummy
            call SetPlayerAllianceStateBJ(plrPatrol, plrDummy,  bj_ALLIANCE_ALLIED_VISION)
            call SetPlayerAllianceStateBJ(plrDummy,  plrPatrol, bj_ALLIANCE_ALLIED_VISION)
            call SetPlayerAllianceStateBJ(plrTarget, plrPatrol, bj_ALLIANCE_UNALLIED)
            call SetPlayerAllianceStateBJ(plrTarget, plrDummy,  bj_ALLIANCE_UNALLIED)
            call SetPlayerAllianceStateBJ(plrPatrol, plrTarget, bj_ALLIANCE_UNALLIED)
            call SetPlayerAllianceStateBJ(plrDummy,  plrTarget, bj_ALLIANCE_UNALLIED)
   
            // Bases
            set iBaseTarget = udg_PatrolBaseTarget
            set rctPatrolBase = null
            set rctTargetBase = null
            set i = 0
            loop
                exitwhen i >= udg_PatrolBasesCount or (rctPatrolBase != null and rctTargetBase != null)
               
                set rectAssigned = false
               
                if rctPatrolBase == null and RectContainsPlayerStart(udg_PatrolBaseRegions[i], plrPatrol) then
                    set rctPatrolBase = udg_PatrolBaseRegions[i]
                    set locPatrolBase = Location(GetRectCenterX(rctPatrolBase), GetRectCenterY(rctPatrolBase))
                    set locBaseRally = udg_PatrolBaseRallyPoints[i]
                    set rgnPatrolBase = CreateRegion()
                    call RegionAddRect(rgnPatrolBase, rctPatrolBase)
                   
                    set rectAssigned = true
                endif
               
                if not rectAssigned and rctTargetBase == null and RectContainsPlayerStart(udg_PatrolBaseRegions[i], plrTarget) then
                    set rctTargetBase = udg_PatrolBaseRegions[i]
                    set locTargetBase = Location(GetRectCenterX(rctTargetBase), GetRectCenterY(rctTargetBase))
                    set rgnTargetBase = CreateRegion()
                    call RegionAddRect(rgnTargetBase, rctTargetBase)
                endif
               
                set i = i+1
            endloop
           
            // Base/Player Triggers
            set trgBaseEnter = CreateTrigger()
            call TriggerRegisterEnterRegion(trgBaseEnter, rgnPatrolBase, function BaseEnterFltrAcn)
            call DisableTrigger(trgBaseEnter)

            set trgTargetEntersBase = CreateTrigger()
            call TriggerRegisterEnterRegion(trgTargetEntersBase, rgnPatrolBase, function TargetAggroFltrAcn)
            call DisableTrigger(trgTargetEntersBase)
           
            set trgPlrPatrolAttacked = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(trgPlrPatrolAttacked, plrPatrol, EVENT_PLAYER_UNIT_ATTACKED, function TargetAggroFltrAcn)
            call DisableTrigger(trgPlrPatrolAttacked)
           
            set trgPatrolDead = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(trgPatrolDead, plrPatrol, EVENT_PLAYER_UNIT_DEATH, function PatrolBaseDeadFltrAcn)
            call TriggerRegisterPlayerUnitEvent(trgPatrolDead, plrDummy, EVENT_PLAYER_UNIT_DEATH, function PatrolBaseDeadFltrAcn)
            call DisableTrigger(trgPatrolDead)
           
            set trgTargetDead = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(trgTargetDead, plrTarget, EVENT_PLAYER_UNIT_DEATH, function TargetBaseDeadFltrAcn)
            call TriggerRegisterEnterRegion(trgTargetDead, rgnTargetBase, function TargetBaseDeadFltrAcn)
            call DisableTrigger(trgTargetDead)
   
            set trgDummyDies = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(trgDummyDies, plrDummy, EVENT_PLAYER_UNIT_DEATH, null)
            call TriggerAddCondition(trgDummyDies, function DummyDiesCndAcn)
            call DisableTrigger(trgDummyDies)
           
            // Camps
            set trgCampEnter = CreateTrigger()
            set cntCamps = udg_PatrolCampsCount
            set i = 0
            loop
                exitwhen i >= cntCamps
               
                set camps[i] = camp.create(udg_PatrolCampRegions[i], udg_PatrolCampRallyPoints[i], udg_PatrolCampFacingPoints[i], udg_PatrolCampTargets[i])
                call GroupEnumUnitsInRect(camps[i].grp, camps[i].rct, function OwnerDummyNoAlertFltr)
                call SaveInteger(hshCampTargets, GetHandleId(camps[i].rgn), 0, i)
                call SaveInteger(hshCampTargets, GetHandleId(camps[i].trgLeaveQ), 0, i)
                call TriggerAddAction(camps[i].trgLeaveQ, function CampsLeaveQAcn)
                call DisableTrigger(camps[i].trgLeaveQ)
                call TriggerRegisterEnterRegion(trgCampEnter, camps[i].rgn, function OwnerDummyNoAlertFltr)
               
                set i = i+1
            endloop
            call TriggerAddAction(trgCampEnter, function CampEnterAcn)
            call DisableTrigger(trgCampEnter)
        endif
    endfunction
//===============================================================================
//===============================================================================

//===============================================================================
//==== INITIALIZER ==============================================================
//===============================================================================
    private function InitPatrol takes nothing returns nothing
        call TriggerRegisterTimerExpireEvent(trgBaseLeave, tmrBaseLeave)
        call TriggerAddCondition(trgBaseLeave, function BaseLeaveAcn)
        call DisableTrigger(trgBaseLeave)
       
        call TriggerAddAction(trgBaseLeaveQ, function BaseLeaveQAcn)
        call DisableTrigger(trgBaseLeaveQ)
       
        call TriggerRegisterTimerExpireEvent(trgEncounterCheck, tmrEncounterCheck)
        call TriggerAddCondition(trgEncounterCheck, function EncounterCheckCndAcn)
        call DisableTrigger(trgEncounterCheck)
       
        call TriggerAddCondition(trgEncounterCheckQ, function EncounterCheckQCndAcn)
        call DisableTrigger(trgEncounterCheckQ)
       
        call TriggerRegisterTimerExpireEvent(trgRunnersPing, tmrRunnersPing)
        call TriggerAddCondition(trgRunnersPing, function RunnersPingCndAcn)
        call DisableTrigger(trgRunnersPing)
    endfunction
//===============================================================================
//===============================================================================
endlibrary
Unit type classifications:
---------------------------------------
UNIT_TYPE_ANCIENT
UNIT_TYPE_ATTACKS_FLYING
UNIT_TYPE_ATTACKS_GROUND
UNIT_TYPE_DEAD
UNIT_TYPE_ETHEREAL
UNIT_TYPE_FLYING
UNIT_TYPE_GIANT
UNIT_TYPE_GROUND
UNIT_TYPE_HERO
UNIT_TYPE_MAGIC_IMMUNE
UNIT_TYPE_MECHANICAL
UNIT_TYPE_MELEE_ATTACKER
UNIT_TYPE_PEON
UNIT_TYPE_PLAGUED
UNIT_TYPE_POISONED
UNIT_TYPE_POLYMORPHED
UNIT_TYPE_RANGED_ATTACKER
UNIT_TYPE_RESISTANT
UNIT_TYPE_SAPPER
UNIT_TYPE_SLEEPING
UNIT_TYPE_SNARED
UNIT_TYPE_STRUCTURE
UNIT_TYPE_STUNNED
UNIT_TYPE_SUMMONED
UNIT_TYPE_TAUREN
UNIT_TYPE_TOWNHALL
UNIT_TYPE_UNDEAD
Patrol Config
  Events
  Conditions
  Actions
    -------- See Variables list for more settings --------
    -------- Players --------
    Set PatrolPlayerPatrol = (Random player from (All players controlled by a Computer player))
    Set PatrolPlayerTarget = (Random player from (All players controlled by a User player))
    Set PatrolPlayerDummy = (Random player from (All players controlled by a None player))
    -------- Guard Types --------
    Set PatrolGuardTypesCount = 2
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        (Race of PatrolPlayerPatrol) Equal to Human
      Then - Actions
        Set PatrolGuardTypes[0] = Footman
        Set PatrolGuardTypes[1] = Knight
      Else - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            (Race of PatrolPlayerPatrol) Equal to Orc
          Then - Actions
            Set PatrolGuardTypes[0] = Grunt
            Set PatrolGuardTypes[1] = Tauren
          Else - Actions
            If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              If - Conditions
                (Race of PatrolPlayerPatrol) Equal to Undead
              Then - Actions
                Set PatrolGuardTypes[0] = Ghoul
                Set PatrolGuardTypes[1] = Abomination
              Else - Actions
                Set PatrolGuardTypes[0] = Archer
                Set PatrolGuardTypes[1] = Huntress
    -------- Excluded Types --------
    -------- Many unit types already covered by excluded classifications below --------
    Set PatrolExcludeTypesCount = 1
    Set PatrolExcludeTypes[0] = Chicken
    -------- Excluded Classifications --------
    -------- Listed in Trigger Comment above --------
    Set PatrolExcludeClassesCount = 5
    Custom script: set pubExcludeClasses[0] = UNIT_TYPE_STRUCTURE
    Custom script: set pubExcludeClasses[1] = UNIT_TYPE_HERO
    Custom script: set pubExcludeClasses[2] = UNIT_TYPE_SUMMONED
    Custom script: set pubExcludeClasses[3] = UNIT_TYPE_MECHANICAL
    Custom script: set pubExcludeClasses[4] = UNIT_TYPE_PEON
    -------- Bases --------
    -------- Automatically assigned to correct player --------
    Set PatrolBasesCount = 2
    Set PatrolBaseRegions[0] = Base1 <gen>
    Set PatrolBaseRegions[1] = Base2 <gen>
    Set PatrolBaseRallyPoints[0] = (Center of Base1Rally <gen>)
    Set PatrolBaseRallyPoints[1] = (Center of Base2Rally <gen>)
    -------- Camps --------
    Set PatrolCampsCount = 3
    Set PatrolCampRegions[0] = PatrolCamp1 <gen>
    Set PatrolCampRegions[1] = PatrolCamp2 <gen>
    Set PatrolCampRegions[2] = PatrolCamp3 <gen>
    Set PatrolCampRallyPoints[0] = (Center of PatrolCamp1Rally <gen>)
    Set PatrolCampRallyPoints[1] = (Center of PatrolCamp2Rally <gen>)
    Set PatrolCampRallyPoints[2] = (Center of PatrolCamp3Rally <gen>)
    Set PatrolCampFacingPoints[0] = (Center of PatrolCamp1Facing <gen>)
    Set PatrolCampFacingPoints[1] = (Center of PatrolCamp2Facing <gen>)
    Set PatrolCampFacingPoints[2] = (Center of PatrolCamp3Facing <gen>)
    -------- Targets - index of camp array - PatrolCampsCount targets base --------
    -------- Incremented when a patrol group is sent out --------
    Set PatrolBaseTarget = 0
    Set PatrolCampTargets[0] = 1
    Set PatrolCampTargets[1] = 2
    Set PatrolCampTargets[2] = PatrolCampsCount
    -------- Sounds --------
    Set PatrolRunnersSound = Warning <gen>
    Set PatrolBaseAlertedSound = QuestFailed <gen>
    Set PatrolTargetAttacksSound = GoodJob <gen>
    -------- Configure and turn on script --------
    Custom script: call ConfigPatrolScript()
    Custom script: call TogglePatrolScript(false)
Patrol State Change
  Events
    Game - PatrolSystemState becomes Equal to -1.00
    Game - PatrolSystemState becomes Equal to 0.00
    Game - PatrolSystemState becomes Equal to 1.00
    Game - PatrolSystemState becomes Equal to 1.01
    Game - PatrolSystemState becomes Equal to 1.02
    Game - PatrolSystemState becomes Equal to 1.03
    Game - PatrolSystemState becomes Equal to 0.10
    Game - PatrolSystemState becomes Equal to 1.10
    Game - PatrolSystemState becomes Equal to 1.11
    Game - PatrolSystemState becomes Equal to 1.12
    Game - PatrolSystemState becomes Equal to 1.13
  Conditions
  Actions
    -------- -1: Script Destroyed --------
    -------- 0: Script Off --------
    -------- 1: Script On --------
    -------- +0.1: Attack Mode On --------
    -------- +0.01: Patroller(s) Running to base --------
    -------- +0.02: Patrol Base Alerted --------
    -------- +0.03: Patrol Player Attacked by Target --------
    If (PatrolSystemState Equal to -1.00) then do (Display to (All players) the text: Patrol Script Destroyed) else do (Do nothing)
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        PatrolSystemState Greater than or equal to 1.00
      Then - Actions
        Game - Display to (All players) the text: Patrol Script On
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            (PatrolSystemState Equal to 1.01) or (PatrolSystemState Equal to 1.11)
          Then - Actions
            Game - Display to (All players) the text: Patroller(s) Running
          Else - Actions
            If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              If - Conditions
                (PatrolSystemState Equal to 1.02) or (PatrolSystemState Equal to 1.12)
              Then - Actions
                Game - Display to (All players) the text: Patrol Base Alerted
              Else - Actions
                If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                  If - Conditions
                    (PatrolSystemState Equal to 1.03) or (PatrolSystemState Equal to 1.13)
                  Then - Actions
                    Game - Display to (All players) the text: Patrol Player Attacked
                  Else - Actions
      Else - Actions
        Game - Display to (All players) the text: Patrol Script Off
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        Or - Any (Conditions) are true
          Conditions
            PatrolSystemState Equal to 0.10
            PatrolSystemState Greater than or equal to 1.10
      Then - Actions
        Game - Display to (All players) the text: Attack Mode On
      Else - Actions
        Game - Display to (All players) the text: Attack Mode Off
/*
v1.2.1
 - Script now turned off when Patrol player dies
 - Attack Mode now turned off when Target player dies
 - Use unit current acquisition range if rngEncounter <= 0 and/or rngEncounterGroup <= 0
 - Base region assigning now based on player start location
 - Camp Leaving now requeued if camp still full
 - Added check for idle patrols in Patrol base (that lost their orders after getting stuck)
 - Added several checks for units being alive
 - Misc fixes/improvements
 - Switched to generic map

v1.2.0
- Separated ConfigPatrol() into ApplyPatrolSettings() and ConfigPatrolScript()
- When disabling script, Enter Base and Enter Camps triggers will now remain enabled until no units are patrolling (unless forced)
- When DestroyPatrolScript fails, AutoDestruct is now turned on and script disabled
- Improved/fixed AutoDestruct behaviour
- Fixed a bug when changing PatrolSystemState
- Replaced Enter Camps trigger array with a single trigger
- Replaced Camp arrays with a struct array
- Replaced TriggerGetCampIndex() with a hashtable
- Fixed some minor leaks
- General code optimization/refactoring

v1.1.2
- Replaced Runners unit and effect arrays with a hashtable
- Some refactoring regarding ^, removed some redundancy
- Fixed trigger for dying patrols
- Misc small fixes/improvements
- PatrolAttackToggle() renamed to TogglePatrolAttack()
- PatrolScriptToggle() renamed to TogglePatrolScript()
- PatrolConfig() renamed to ConfigPatrol()
- PatrolScriptDestroy() renamed to DestroyPatrolScript()

v1.1.1 (fix)
- Fixed leaks (nulled agents)

v1.1.1
- More flexible configuration
- Only units visible to patrols can alert
- Attack mode enabled when plrTarget attacks plrPatrol or enters its base
- Added sound for ^
- Check if base alerted sound is playing before playing
- Initial value set for sound UDGs ("No sound")
- udg_PatrolSystemState: second decimal now reflects alert state
*/
Default melee game initialization for all players
Init
  Events
    Map initialization
  Conditions
  Actions
    Visibility - Disable black mask
    Visibility - Disable fog of war
    Player - Set Player 1 (Red) Current gold to 1000000000
    Player - Set Player 1 (Red) Current lumber to 1000000000
    Player - Set Player 1 (Red) Food cap to 1000000000
    Player - Set Player 1 (Red) Food used to 0
    Trigger - Run Patrol_Config <gen> (checking conditions)
    Trigger - Run Create_Units <gen> (ignoring conditions)
Create Units
  Events
    Player - Player 1 (Red) types a chat message containing -units as An exact match
  Conditions
  Actions
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        (Race of PatrolPlayerPatrol) Equal to Human
      Then - Actions
        Unit - Create 4 Footman for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
        Unit - Create 4 Knight for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
        Unit - Create 5 Rifleman for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
      Else - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            (Race of PatrolPlayerPatrol) Equal to Orc
          Then - Actions
            Unit - Create 4 Grunt for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
            Unit - Create 4 Tauren for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
            Unit - Create 5 Troll Headhunter for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
          Else - Actions
            If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              If - Conditions
                (Race of PatrolPlayerPatrol) Equal to Undead
              Then - Actions
                Unit - Create 4 Ghoul for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
                Unit - Create 4 Abomination for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
                Unit - Create 5 Crypt Fiend for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
              Else - Actions
                Unit - Create 4 Archer for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
                Unit - Create 4 Huntress for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
                Unit - Create 5 Dryad for PatrolPlayerPatrol at (Random point in Base2 <gen>) facing Default building facing degrees
Move Units outside base region
  Events
    Time - Every 5.00 seconds of game time
  Conditions
  Actions
    Unit Group - Pick every unit in (Units owned by PatrolPlayerPatrol matching ((Base2 <gen> contains (Matching unit)) Equal to False)) and do (Actions)
      Loop - Actions
        Unit - Order (Picked unit) to Move To (Random point in Base2 <gen>)
Toggle Script
  Events
    Player - Player 1 (Red) types a chat message containing -toggle as An exact match
  Conditions
  Actions
    Custom script: call TogglePatrolScript(false)
Attack Mode On
  Events
    Player - Player 1 (Red) types a chat message containing -attackon as An exact match
  Conditions
  Actions
    Custom script: call TogglePatrolAttack(true)
Attack Mode Off
  Events
    Player - Player 1 (Red) types a chat message containing -attackoff as An exact match
  Conditions
  Actions
    Custom script: call TogglePatrolAttack(false)
Destroy Script
  Events
    Player - Player 1 (Red) types a chat message containing -destroy as An exact match
  Conditions
  Actions
    Custom script: call DestroyPatrolScript(false)