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