Randomised Patrols

Back to Index

As described here, simple patrols can be set up quite easily by giving guards a travel package that takes them to a target point, from which they can then either go to the next target point or return to the original position. However, this is a very strict and predictable routine, and a more interesting and realistic patrol cycle can be created by giving guards a partially random path to follow.

In principle, all we need to do is to give the guards a target point, and once they have reached it, pick the target point at random. Although the scripting language is a bit limited, the necessary functions can be simulated by using a quest (see the wiki) and a script on each guard NPC. This method works best if each guard is a unique NPC; if several guards share the same base object (like most of the guards in Oblivion) they will all move together in a bunch.

The basic idea is to give each guard a travel package which has a guard-specific marker as its terminator. The guard is then given an initial target, and the marker is moved to that position. Once the guard has reached the position, a new target is chosen and the marker is moved there. The travel package will re-evaluate automatically and direct the guard to the new position, and this will carry on for as long as required (in other words until another package takes over).

Step 1: Plan the routes

Below is an example of a patrol grid with five points. These points can be as far apart or as close together as you like, and connected in as many ways as suitable.

We can represent this in a table:
Source Point Possible Targets
AB, D
BA, C, D, E
CB
DA, B, E
EB, D

Decide what the chances are of choosing a particular path at each point, and extend the table:
Source Point Targets Choices
AB, D
Target Chance % Range
B500-49
C5050-100
BA, C, D, E
Target Chance % Range
A250-24
C2525-49
D2550-74
E2575-99
CB
Target Chance % Range
B1000-99
DA, B, E
Target Chance % Range
A300-29
B4030-69
E3070-100
EB, D
Target Chance % Range
B500-49
C5050-100

By changing the percentages you can make some paths more common than others, or force guards to choose a particular path at a certain point. Working out a good pattern is not just a matter of allocation even chances; if you have several target points close by, it is likely that guards will wander around in that area much more than elsewhere, so you may need to increase the chance of choosing a path leading away from the concentration. Once all the scripting has been completed a certain amount of trial and error may be necessary to fine-tune the paths.

Step 2: Position Markers

Drop a Xmarker at each target point and name it something relevant, like PatrolMarkerA, PatrolMarkerB etc. Also create separate XMarkers for each of your guard NPCs and give them corresponding names, e.g. GuardATargetMarker etc. It doesn't matter where you place those markers since they will be moved around by the scripts to where they're needed.

Step 3: Travel Packages

Create a separate travel package for each guard, with the following parameters:
1. Check the 'Must reach location' flag.
2. Location: choose 'near reference' and pick the relevent marker, e.g. GuardATargetMarker. Set the radius to something small like 32.
3. Optionally, set the time and duration on the Schedule tab.

Name the package appropriately, e.g. GuardAPatrolPackage. Do this for each guard, and assign the travel packages to the NPCs. If you have not assigned a time and duration, make sure that the package is the last one in the list for each guard.

That was the easy part. The fun starts now.

Step 4: Quest Script

Most of the processing will be handled by a quest, using quest stages, but we also need a quest script for some basic data. Create the following script, and make sure it's marked as a Quest Script:

ScriptName CFScriptPatrolScheduler

ref NextTargetRef
int iCurTarget
int iNextTarget
int iRnd
int iTemp

NextTargetRef is used to return the next target point when a guard has to choose.
iCurTarget and iNextTarget identify the last point reached and the next one the guard is aiming for. We'll use integers to represent the points, where 1 = Point A etc.
iRnd is used in the randomiser.
iTemp is necessary because of certain scripting language limitation, as you'll see in Stage 98 of the quest.

Step 5: The Patrol Scheduler Quest

This is the main beast that handles target choices. Create a new quest and call it something short and simple, like PSQ, and attach the script created in Step 4 to it. Set the priority to a lowish number like 40, and set the 'start game enabled' and 'allow repeated stages' flags.

We'll need two stages for each of our target points, plus three special stages. I used stages 1 to 5 and 101 to 105 for the target point handling, and 98 to 100 for the special stages. Once you have set these up you should have the following stage list:

StageUse
1Chooses next target from point A.
2Chooses next target from point B.
3Chooses next target from point C.
4Chooses next target from point D.
5Chooses next target from point E.
98Applies the new target.
99Default handling if a guard is not at a target point.
100Determines where the guard is an which stage to use next.
101Sets the next target as Point A.
102Sets the next target as Point B.
103Sets the next target as Point C.
104Sets the next target as Point D.
105Sets the next target as Point E.

For each stage, right-click in the 'Log Entry' list and create an empty entry. We don't need any text, we just need the associated result script. Enter the following scripts for stages 1 to 5:

StageResult Script
1 ; find next target from point A
set PSQ.iRnd to GetRandomPercent
if PSQ.iRnd < 50
set PSQ.iNextTarget to 2 ; point B
else
set PSQ.iNextTarget to 3 ; point C
endif
2 ; find next target from point B
set PSQ.iRnd to GetRandomPercent
if PSQ.iRnd < 25
set PSQ.iNextTarget to 1 ; point A
elseif PSQ.iRnd < 50
set PSQ.iNextTarget to 3 ; point C
elseif PSQ.iRnd < 75
set PSQ.iNextTarget to 4 ; point D
else
set PSQ.iNextTarget to 5 ; point E
endif
3 ; find next target from point C
set PSQ.iNextTarget to 2 ; point B
4 ; find next target from point D
set PSQ.iRnd to GetRandomPercent
if PSQ.iRnd < 30
set PSQ.iNextTarget to 1 ; point A
elseif PSQ.iRnd < 70
set PSQ.iNextTarget to 2 ; point B
else
set PSQ.iNextTarget to 5 ; point E
endif
5 ; find next target from point E
set PSQ.iRnd to GetRandomPercent
if PSQ.iRnd < 50
set PSQ.iNextTarget to 2 ; point B
else
set PSQ.iNextTarget to 4 ; point C
endif

Each of these scripts simply represents the chances worked out in Step 1. For stages 101 to 105, enter the following:

StageResult Script
101 ; Point A is the next target
set PSQ.NextTargetRef to PatrolMarkerA
102 ; Point B is the next target
set PSQ.NextTargetRef to PatrolMarkerB
103 ; Point C is the next target
set PSQ.NextTargetRef to PatrolMarkerC
104 ; Point D is the next target
set PSQ.NextTargetRef to PatrolMarkerD
105 ; Point E is the next target
set PSQ.NextTargetRef to PatrolMarkerE

Finally, the extra three stages:

StageResult Script
98 ; apply new target
set PSQ.iTemp to (PSQ.iNextTarget + 100)
SetStage PSQ PSQ.iTemp
99 ; no starting point - head to default point A
set PSQ.iNextTarget to 1
100 if (PSQ.iCurTarget >= 1) && (PSQ.iCurTarget <= 5)
SetStage PSQ PSQ.iCurTarget
else
SetStage PSQ 99
endif
SetStage PSQ 98

And that's it as far as the quest is concerned.

How does it work? When a guard needs a new target point, the guard's script (see below) will set the quest stage to 100. This will kick off the result script which checks where there guard currently is and set the appropriate stage to determine the new target, or set a default target if the guard is not at a target point. Once the new target number has been determined, the quest is set to stage 98, which will use the target number to make the quest set the appropriate target marker reference, which can then be used by the guard's script when control returns to the guard.

Step 6: The Guard Script

Create the following script for each guard and attach it to the NPC:

ScriptName GuardAScript

short iPatrolTarget
short iLastTarget
ref NextTarget

begin OnPackageEnd GuardAPatrolPackage
if iPatrolTarget > 0
set iLastTarget to iPatrolTarget
set iPatrolTarget to 0
endif
end

begin GameMode
if iPatrolTarget == 0
; need a new target
; this section can run even if another package like sleep is executing
; since once a target has been set, it just hangs around until needed
; when the patrol package next executes

set PSQ.iCurTarget to iLastTarget
SetStage PSQ 100
set iPatrolTarget to PSQ.iNextTarget
set NextTarget to PSQ.NextTargetRef
; just a test message to check that guards are getting targets
; message "Guard A patrol target: %.0f", iPatrolTarget
GuardATargetMarker.MoveToMarker NextTarget
endif
end

Make sure that for each guard you use the correct package name in the OnPackageEnd block, and that you use the correct marker name at the end of the GameMode block.

How does it work? Initially the guard will not have a patrol target. The GameMode block detects this and uses the PSQ quest (setting its stage to 100) to get a new target; since the guard won't be at a patrol point the new target will be the default defined in stage 99. The guard's marker is moved to the position, and then we just wait until the guard's patrol package evaluates. And off marches the guard...

Once the guard has reached the target position, the OnPackageEnd block executes. We reset the iPatrolTarget flag, and in the next iteration of the GameMode block we request a new target just as before, except that this time the we know at which patrol point the guard is. Since the patrol package has just ended, Oblivion will check which package to execute next, and if the patrol package is still valid (depending on its conditions and time schedule), the guard marches off to the next point.

This process will carry on for as long as the GuardAPatrolPackage is chosen by the system as the active AI package. When another package is chosen, the patrol stops, but is automatically resumed when the system chooses the GuardAPatrolPackage again.


A Further Possibility: Preventing Doubling Back

A simple check we might want to implement is to stop a guard from going back to where he just came from. In a proper programming environment a good solution would be to keep track of the last n positions, and change the weightings assigned to each choice depending on when and how often a guard has visited a particular point. This is not easily possible in Oblivion, but at least we can make sure a guard doesn't double back unless it's the only option.

Change the quest script in Stage 4 and add the following variables:

int iAllowDoubleback
int iPrevTarget

Then change the result scripts for the following stages by adding the given line to the end of each script:

StageResult Script
1 set PSQ.iAllowDoubleback to 25
2 set PSQ.iAllowDoubleback to 5
3 set PSQ.iAllowDoubleback to 100
4 set PSQ.iAllowDoubleback to 25
5 set PSQ.iAllowDoubleback to 25

Target point A only has two connections, so we don't mind too much if a guard goes back where he came from. On the other hand, target point B has many connections, so we don't want the guard going back. And on the third hand, point C has only one connection, and the guard must go back where he came from.

Now change the result script for stage 100 to the following:

StageResult Script
100 if (PSQ.iCurTarget >= 1) && (PSQ.iCurTarget <= 5)
SetStage PSQ PSQ.iCurTarget
else
SetStage PSQ 99
endif
if PSQ.iPrevTarget == PSQ.iNextTarget
set PSQ.iRnd to GetRandomPercent
if PSQ.iRnd >= PSQ.iAllowDoubleback
; can't double back, look for another target
SetStage PSQ 100
endif
endif
SetStage PSQ 98

And lastly, change the guard script to this:

ScriptName GuardAScript

short iPatrolTarget
short iLastTarget
short iPrevTarget ; ****** added this line
ref NextTarget

begin OnPackageEnd GuardAPatrolPackage
if iPatrolTarget > 0
set iPrevTarget to iLastTarget ; ****** added this line
set iLastTarget to iPatrolTarget
set iPatrolTarget to 0
endif
end

begin GameMode
if iPatrolTarget == 0
; need a new target
; this section can run even if another package like sleep is executing
; since once a target has been set, it just hangs around until needed
; when the patrol package next executes

set PSQ.iCurTarget to iLastTarget
set PSQ.iPrevTarget to iPrevTarget ; ****** added this line
SetStage PSQ 100
set iPatrolTarget to PSQ.iNextTarget
set NextTarget to PSQ.NextTargetRef
; just a test message to check that guards are getting targets
; message "Guard A patrol target: %.0f", iPatrolTarget
GuardATargetMarker.MoveToMarker NextTarget
endif
end

Warning
Point C has only one possible path. If you set iAllowDoubleback to 0 in stage 3, the guards will get stuck!

Fine-tuning

If we take the patrol grid shown here and assign equal chances to all paths, it is highly likely that guards will congregate in the central area and only infrequently venture into the outer area.

The chance of a guard actually completing a circuit of the outer points is extremely low (I'm too lazy to work it out mathematically though). How do we make sure that guards visit the outer points as often as they visit the inner ones? We can do this mathematically, or by trial and error, or by rough estimation. The last one works for me. :)

For example, at each of the inner points we can assume that a guard will either stay on the inside, or head outwards. So we give the outward path a 50% chance, and divide the other 50% equally between the other paths.

Alternatively, multiple schedulers can be used - e.g. one where the guards mainly patrol the centre, one where the guards patrol equally, and one where the guards mainly patrol the outside. In fact, this can all be dealt with by a single scheduler as long as we include one more flag to indicate a guard's preferred area of operation, and adjusting the checks in steps 1 to n as needed.

Several more possibilites spring to mind, such as interrupting patrolling with wander packages at the current location etc. Try it out, have fun, and don't forget to allow your guards at least a little bit of sleep!


Last update 2006-09-04