Left 4 Dead 2/Scripting/Response System

From Valve Developer Community
Jump to navigation Jump to search
Icon-callout-under-construction-blue.png
This is a draft page. It is a work in progress open to editing by anyone.
Remember to check for any notes left by the tagger at this article's talk page.
Icon-callout-cleanup.png
This article or section needs to be cleaned up to conform to a higher standard of quality.

The response system used by Left 4 Dead 2 can be extended through VScript by creating custom decision rules. These rules allow scripts to evaluate criteria and select responses dynamically based on game state, similar to the built-in response rules used by the engine.

VScript response rules use the same general concept as the traditional response rule system, but instead of being defined through KeyValues files, rules are created using Squirrel tables.

A response rule consists of three main parts:

  • name - An arbitrary rule name, mainly used for debugging.
  • criteria - Conditions that determine whether a rule matches.
  • responses - Actions performed when a rule is selected.

Response system structure

A response rule is a Squirrel table containing a name, criteria, and responses:

{
	name = "ExampleRule",

	criteria =
	[
		[ "concept", "TLK_REMARK" ],
		[ "who", "Coach" ],
		[ "subject", "ExampleSubject" ],
		[ "distance", 0, 500 ]
	],

	responses =
	[
		{
			scenename = "scenes/Coach/MoveOn01.vcd"
		}
	]
}

Multiple rules can be stored in an array:

[
	{
		name = "Rule1",
		criteria = [],
		responses = []
	},

	{
		name = "Rule2",
		criteria = [],
		responses = []
	}
]

Criteria

Criteria are used to determine if a rule is valid. Every criterion in a rule must match before the response can be selected.

Criteria can be either static comparisons or functions.

Static criteria

Static criteria compare a fact from the response query against a value.

A two-value criterion checks if a fact equals a value:

[ "concept", "TLK_HELP_ME" ]

This matches when the query contains:

concept = "TLK_HELP_ME"

A three-value criterion checks whether a number is within a range:

[ "distance", 0, 500 ]

This matches values between 0 and 500.

A missing value can be used as an unlimited range:

[ "distance", 500, null ]

Matches values greater than or equal to 500.

Function criteria

For information about what the query contains, see Query facts ↓

Criteria can also be functions that return true or false.

Icon-Important.pngImportant: When accessing functions or variables defined in the global response rule table, you must prefix them with g_rr..

Example:

function IsNotInCombat(query) // query param is IMPORTANT!!! DO NOT REMOVE!!!
{
	return query.incombat == 0;
}

// In the rules
criteria =
[
	[ IsNotInCombat ]
]

Example 2:

function SomeFunction() {
	//...
}

function SomeFunctionABC(query) {
	//...
}

MyGlobalVariable <- "ABC";

function IsNotInCombat(query) // query param is IMPORTANT!!! DO NOT REMOVE!!!
{
	g_rr.SomeFunction();
	
	g_rr.SomeFunctionABC(query);
	
	print(g_rr.MyGlobalVariable);

	return query.incombat == 0;
}

// In the rules
criteria =
[
	[ IsNotInCombat ]
]

Functions allow rules to perform custom checks that cannot be represented by simple comparisons.

Another example is using the param query to see if a survior is alive

function IsGamblerAlive(query) // query param is IMPORTANT!!! DO NOT REMOVE!!!
{
	return query.isgambleralive == 1;
}

criteria =
[
	[ IsGamblerAlive ]
]

Common criteria

concept

The concept represents the type of response being requested.

Examples:

[ "concept", "TLK_HELLO" ]
[ "concept", "TLK_HELP_ME" ]
[ "concept", "TLK_RELOAD" ]

who

who is not an entity lookup. It is a response query fact containing the speaker identifier supplied by the caller.

Built-in survivor responses commonly use internal survivor names:

  • Coach
  • Gambler (Nick)
  • Mechanic (Ellis)
  • Producer (Rochelle)
  • Biker (Francis)
  • Manager (Louis)
  • NamVet (Bill)
  • TeenGirl (Zoey)

However, custom scripts may provide any string value.

Example:

[ "who", "Virgil" ]

subject

The subject usually refers to the entity the response is directed toward.

Example:

[ "subject", "PlayerRemarkC1M1ThroughThisDoorCoach" ]

Responses

Responses define what happens when a rule matches.

A response can execute a scene, sound, or script function.

Scene responses

A response containing scenename plays a VCD scene:

responses =
[
	{ scenename = "scenes/Coach/MoveOn01.vcd" }
]

Script responses

A response can execute a VScript function:

function ExampleResponse(speaker, query)
{
	printl("Response executed")
}

responses =
[
	{ func = ExampleResponse }
]

The function receives:

  • speaker - the entity performing the response.
  • query - the response query table.

Response parameters

Responses can contain additional parameters controlling behavior.

Example:

{
	scenename = "scenes/Coach/MoveOn01.vcd",
	delay = 2,
	odds = 50
}

Common parameters:

Parameter Description
nodelay Removes the additional speaking delay.
defaultdelay Adds the default response delay.
odds Chance that the response is selected.
speakonce Prevents the response from being used more than once.
weight Changes selection priority between responses.
respeakdelay Prevents the same response from playing again for a period of time.
applycontext
applycontexttoworld

Group parameters

Response groups control how responses are selected and repeated.

group_params = RGroupParams(
{
	permitrepeats = false,
	sequential = true,
	norepeat = false
})

Ususally it can just be group_params = RGroupParams().

Available options:

  • permitrepeats - Allows responses to repeat before all responses have been used.
  • sequential - Selects responses in order instead of randomly.
  • norepeat - Disables the response group after all responses have been used.
  • matchonce - Disables the rule after it matches once.

Creating a response system

Criteria examples

criteria = [
	[ "concept", "onSeeEnemy" ], // arrays of two entries are considered to be fact = value
	[ "speaker", "coach" ],
	[ "numAllies", 1, 4 ],	// arrays of three entries are considered to be fact >= x && fact <= y
	[ "enemyType", "smoker" ],
	[ @(query) (query.GameTime) < 30 ] // arrays of one entry are expected to be functions (In this entry, its an anonymous function)
],
Icon-Important.pngImportant: g_rr is needed in the reponse rules when declaring criteria rules as a variable.
// if you want to share a criterion between many rules, you can declare it as its own variable like this:
CriterionIsNotCoughing <- [ "Coughing", 0 ]
CriterionIsc6m3_port  <- [ "map", "c6m3_port" ]  //use for the moveon example below to scope the rule change
CriterionIsAwardProtector  <- [ "awardname","Protector" ]

// and then include it into the criteria lists as per the examples below. The "g_rr." part
// is a bit of temporary cruft that will go away once we resolve some questions about scope resolution
// in map .nut files.

criteria = [
		[ "concept", "PlayerMoveOn" ], // conceptPlayerMoveOn
		g_rr.CriterionIsNotCoughing,
		[ "Who", "Coach" ],	// isCoach
		g_rr.CriterionIsc6m3_port
	],


Response examples

Confirm:Does onFinish work? Might not be implemented as said here: onFinish = eventually, a script function to call when the scene finishes (currently not implemented).
responses = [
	{ scenename = "coach_see_smoker_1.vcd", // if a 'scenename' key is present, this is expected to be a 'scene' response
	  soundlevel = 80,
	  onFinish = @(query, speaker) speaker.smokersSeen += 1 // expected to be a function
	},
	{
	  func = ZombieFreakout // if a 'func' key is present, this is expected to be a 'do function' response 
	},
	{
	  func = @(query,speaker) speaker.PointAt( query.enemy ) // anonymous functions are ok too
	},
	{
	  scenename = "coach_see_smoker_2.vcd",
	  sndlevel = 90,
	  onFinish = SomeFunc
	}
],
responses = [
	{ 
		func = SetAwardSpeech	
	}
],
responses = [
	{ scenename = "scenes/Gambler/MoveOn01.vcd",
		followup = RThen( "self",  "DemonstrateScriptFollowup", {additionalcontext="whatever"}, 1.23 ) // dispatches a "DemonstrateScriptFollowup" concept back to self
		func = DemoWritingContextToCharacter
	}
],

RThen is a followup event like in the response system in TXT files. All followups need to expose a function and a 'delay' parameter.

The callable gets the following parameters: ( _target, _concept, _contexts, _delay )

RThen(
	"coach",  					// target
	"TLK_FOLLOWUP_WHATEVER", 	// concept
	{ foo = 1, bar = "flarb" }, // contexts to add to the followup  (may be null instead)
	1.5 						// delay
)

Query facts

Todo: Check if then descriptions are correct.

The following table lists the known query facts available when evaluating a response rule. Values shown are examples captured from a live response query.

Note.pngNote: The keyvalues are changed if the mission file has "survivor_set" as "1" for L4D1 Survivors or "2" for L4D2 Survivors
Param "query" list for L4D1 Survivors
{
	[beingjockeyed] 			= 0 (float)
	[numberinsafespot] 			= 4 (float)
	[health] 					= 100 (float)
	[numberofteamdead] 			= 0 (float)
	[SPITTERsKilled] 			= 0 (float)
	[TankActive] 				= 0 (float)
	[CurrentTemporaryHealth] 	= 0 (float)
	[name] 						= 0 (float)
	[hasfirstaidkit] 			= 0 (float)
	[SMOKERsKilled] 			= 0 (float)
	[disttomanager] 			= 2456 (float)
	[numberofteamalive] 		= 4 (float)
	[worldtalk] 				= 0 (float)
	[worldcxm1intro] 			= 1 (float)
	[concept] 					= TLK_REMARK (string)
	[NORMALsKilled] 			= 0 (float)
	[WITCHsKilled] 				= 0 (float)
	[BOOMERsKilled] 			= 0 (float)
	[isnamvetalive] 			= 1 (float)
	[Chapter] 					= 0 (float)
	[isbikeralive] 				= 1 (float)
	[JOCKEYsKilled] 			= 0 (float)
	[disttoproducer] 			= 2543.9 (float)
	[isteengirlalive] 			= 1 (float)
	[gamemode] 					= coop (string)
	[zombiepresentwitch] 		= 1 (float)
	[ZombiePresentNormal] 		= 1 (float)
	[world_auto_saferoomstart] 	= 1 (float)
	[classname] 				= player (string)
	[disttoclosestsurvivor] 	= 2456 (float)
	[Sneaking] 					= 0 (float)
	[movementspeed] 			= 94 (float)
	[disttocoach] 				= 2456 (float)
	[disttoteengirl] 			= 2543.9 (float)
	[instartarea] 				= 0 (float)
	[pouncevictim] 				= 0 (float)
	[LowViolence] 				= 0 (float)
	[disttogambler] 			= 0 (float)
	[who] 						= NamVet (string)
	[map] 						= <map name; eg. c1m1_hotel> (string)
	[healthfrac] 				= 1 (float)
	[NumberOfTeamIncapacitated] = 0 (float)
	[inrescuevehicle] 			= 0 (float)
	[onfire] 					= 0 (float)
	[haspainpills] 				= 0 (float)
	[is_pc] 					= 1 (float)
	[ismanageralive] 			= 1 (float)
	[hasgrenade] 				= 0 (float)
	[worldsaidleavingsafearea] 	= 1 (float)
	[StoreFrontGlass01] 		= 1 (float)
	[team] 						= Survivor (string)
	[speaking] 					= 0 (float)
	[coughing] 					= 0 (float)
	[ZombiesKilledWhileIncapacitated] = 0 (float)
	[timesincecombat] 			= 99999.9 (float)
	[is_console] 				= 0 (float)
	[skill.cfg] 				= 1 (float)
	[ClosestSurvivor] 			= Manager (string)
	[IncapacitatedCount] 		= 0 (float)
	[CurrentHealth] 			= 100 (float)
	[hasmolotov] 				= 0 (float)
	[IsIncapacitated] 			= 0 (float)
	[campaignrandomnum] 		= 25 (float)
	[disttomechanic] 			= 2501.1 (float)
	[randomnum] 				= 80 (float)
	[HUNTERsKilled] 			= 0 (float)
	[TANKsKilled] 				= 0 (float)
	[disttobiker] 				= 2501.1 (float)
	[CHARGERsKilled] 			= 0 (float)
	[intensity] 				= 0.01 (float)
	[timesincegroupincombat] 	= 99999.9 (float)
	[hangingfromtongue] 		= 0 (float)
	[insafespot] 				= 0 (float)
	[disttonamvet] 				= 0 (float)
	[incombatmusic] 			= 0 (float)
	[hangingfromledge] 			= 0 (float)
	[incheckpoint] 				= 0 (float)
	[inbattlefield] 			= 0 (float)
	[incapacitated] 			= 0 (float)
	[activeweapon] 				= SecondPistol (string)
	[distance] 					= 221.494 (float)
	[incombat] 					= 0 (float)
	[beinghealed] 				= 0 (float)
	[campaign] 					= unknown (string)
	[numberoutsidesafespot] 	= 0 (float)
	[onthirdstrike] 			= 0 (float)
	[introactor] 				= Zoey (string)
	[subject] 					= OurSubject (string)
	[TimeAveragedIntensity] 	= 0.01 (float)
}
Param "query" list for L4D2 Survivors
{
	[beingjockeyed] 			= 0 (float)
	[numberinsafespot] 			= 0 (float)
	[incombat] 					= 0 (float)
	[numberofteamdead] 			= 0 (float)
	[SPITTERsKilled] 			= 0 (float)
	[TankActive] 				= 1 (float)
	[CurrentTemporaryHealth] 	= 0 (float)
	[name] 						= 0 (float)
	[hasfirstaidkit] 			= 0 (float)
	[CHARGERsKilled] 			= 0 (float)
	[numberofteamalive] 		= 4 (float)
	[worldtalk] 				= 0 (float)
	[concept] 					= TLK_REMARK (string)
	[NORMALsKilled] 			= 0 (float)
	[worldsrcgrp_ellisstoryinit] = 1 (float)
	[WITCHsKilled] 				= 0 (float)
	[BOOMERsKilled] 			= 0 (float)
	[incapacitated] 			= 0 (float)
	[Chapter] 					= 0 (float)
	[TANKsKilled] 				= 0 (float)
	[JOCKEYsKilled] 			= 0 (float)
	[disttoproducer] 			= 3958.5 (float)
	[gamemode] 					= coop (string)
	[StoreFrontGlass01] 		= 1 (float)
	[ZombiePresentNormal] 		= 1 (float)
	[world_auto_saferoomstart] 	= 1 (float)
	[classname] 				= player (string)
	[disttoclosestsurvivor] 	= 3958.5 (float)
	[Sneaking] 					= 0 (float)
	[movementspeed] 			= 220 (float)
	[disttocoach] 				= 4017.4 (float)
	[isgambleralive] 			= 1 (float)
	[instartarea] 				= 0 (float)
	[pouncevictim] 				= 0 (float)
	[LowViolence] 				= 0 (float)
	[subject] 					= OurSubject (string)
	[who] 						= Gambler (string)
	[map] 						= <map name; eg. c1m1_hotel> (string)
	[healthfrac] 				= 1 (float)
	[health] 					= 100 (float)
	[NumberOfTeamIncapacitated] = 0 (float)
	[inrescuevehicle] 			= 0 (float)
	[onfire] 					= 0 (float)
	[haspainpills] 				= 1 (float)
	[is_pc] 					= 1 (float)
	[zombiepresentwitch] 		= 1 (float)
	[hasgrenade] 				= 0 (float)
	[worldsaidleavingsafearea] 	= 1 (float)
	[zombiepresenttank] 		= 1 (float)
	[team] 						= Survivor (string)
	[speaking] 					= 0 (float)
	[coughing] 					= 0 (float)
	[ZombiesKilledWhileIncapacitated] = 0 (float)
	[timesincecombat] 			= 99999.9 (float)
	[isproduceralive] 			= 1 (float)
	[is_console] 				= 0 (float)
	[skill.cfg] 				= 1 (float)
	[ClosestSurvivor] 			= Producer (string)
	[CurrentHealth] 			= 100 (float)
	[hasmolotov] 				= 0 (float)
	[IncapacitatedCount] 		= 0 (float)
	[campaignrandomnum] 		= 82 (float)
	[disttomechanic] 			= 4130.4 (float)
	[randomnum] 				= 27 (float)
	[HUNTERsKilled] 			= 0 (float)
	[IsIncapacitated] 			= 0 (float)
	[timesincegroupincombat] 	= 99999.9 (float)
	[world_auto_storywait] 		= 1 (float)
	[intensity] 				= 0 (float)
	[inbattlefield] 			= 0 (float)
	[hangingfromtongue] 		= 0 (float)
	[numberoutsidesafespot] 	= 4 (float)
	[distance] 					= 323.772 (float)
	[incombatmusic] 			= 0 (float)
	[hangingfromledge] 			= 0 (float)
	[incheckpoint] 				= 0 (float)
	[insafespot] 				= 0 (float)
	[disttogambler] 			= 0 (float)
	[activeweapon] 				= SecondPistol (string)
	[world_auto_storygate] 		= 1 (float)
	[ismechanicalive] 			= 1 (float)
	[beinghealed] 				= 0 (float)
	[campaign] 					= unknown (string)
	[iscoachalive] 				= 1 (float)
	[onthirdstrike] 			= 0 (float)
	[introactor] 				= Francis (string)
	[SMOKERsKilled]				= 0 (float)
	[TimeAveragedIntensity] 	= 0 (float)
}

General

Fact Type Example Description
concept String TLK_REMARK Response concept.
who String NamVet Speaker identifier.
subject String OurSubject Subject supplied by the caller.
name Float 0
classname String player
team String Survivor

Campaign

Fact Type Example Description
map String c1m1_hotel Current map.
campaign String unknown
gamemode String coop
Chapter Float 0
LowViolence Float 0
skill.cfg Float 1

Health and status

Fact Type Example Description
health Float 100
CurrentHealth Float 100
healthfrac Float 1
CurrentTemporaryHealth Float 0
IsIncapacitated Float 0
incapacitated Float 0
IncapacitatedCount Float 0
NumberOfTeamIncapacitated Float 0
onthirdstrike Float 0
onfire Float 0
beinghealed Float 0
coughing Float 0
Sneaking Float 0

Combat

Fact Type Example Description
incombat Float 0
incombatmusic Float 0
inbattlefield Float 0
timesincecombat Float 99999.9
timesincegroupincombat Float 99999.9
intensity Float 0.01
TimeAveragedIntensity Float 0.01

Special infected interactions

Fact Type Example Description
beingjockeyed Float 0 Is the current survivor being jockeyed?
pouncevictim Float 0 Is the current survivor being pounce by the hunter?
hangingfromtongue Float 0 Is the current survivor being hanged by the Smoker?
hangingfromledge Float 0 Is the current survivor hanging from a ledge?

Inventory

Fact Type Example Description
activeweapon String SecondPistol Current weapon being held.
hasfirstaidkit Float 0
haspainpills Float 0
hasgrenade Float 0
hasmolotov Float 0

Movement and location

Fact Type Example Description
distance Float 221.494 Distance to the seen entity. (eg. info_remarkable)
movementspeed Float 94 Current survivor speed.
ClosestSurvivor String Manager The closest survivor to the seen entity.
disttoclosestsurvivor Float 2456 The closest survivor to the seen entity.
Note.pngNote: Only when "survivor_set" is "1" in the mission file.
Fact Type Example Description
disttobiker Float 2501.1 Francis
disttonamvet Float 0 Bill
disttoteengirl Float 2543.9 Zoey
disttomanager Float 2543.9 Louis
Note.pngNote: Only when "survivor_set" is "2" in the mission file or default if no mission file is found for the map.
Fact Type Example Description
disttocoach Float 2456 Coach
disttogambler Float 0 Nick
disttomechanic Float 2501.1 Ellis
disttoproducer Float 2543.9 Rochelle

Safe room

Fact Type Example Description
insafespot Float 0 Is the survivor in the safe area? (Mission start, check point room, etc.)
numberinsafespot Float 4 How many survivors are in a safe area (Mission start, check point room, etc.)
numberoutsidesafespot Float 0 How many survivors are outside a safe area.
instartarea Float 0 Is the survivor in the starting area (nav area with MISSION_START)
incheckpoint Float 0 Is the survivor in the checkpoint area (nav area with CHECKPOINT)
inrescuevehicle Float 0 Is the survivor in the rescue vehicle area (nav area with RESCUE_VEHICLE)

Director

Fact Type Example Description
TankActive Float 0
ZombiePresentNormal Float 1
zombiepresentwitch Float 1
campaignrandomnum Float 25
randomnum Float 80

Survivor state

Note.pngNote: Only when "survivor_set" is "1" in the mission file.
Fact Type Example Description
isbikeralive Float 1
ismanageralive Float 1
isnamvetalive Float 1
isteengirlalive Float 1
Note.pngNote: Only when "survivor_set" is "2" in the mission file or default if no mission file is found for the map.
Fact Type Example Description
iscoachalive Float 1
isgambleralive Float 1
ismechanicalive Float 1
isproduceralive Float 1

Statistics

Fact Type Example Description
BOOMERsKilled Float 0
CHARGERsKilled Float 0
HUNTERsKilled Float 0
JOCKEYsKilled Float 0
NORMALsKilled Float 0
SMOKERsKilled Float 0
SPITTERsKilled Float 0
TANKsKilled Float 0
WITCHsKilled Float 0
ZombiesKilledWhileIncapacitated Float 0

World contexts

Fact Type Example Description
worldtalk Float 0
worldcxm1intro Float 1
world_auto_saferoomstart Float 1
worldsaidleavingsafearea Float 1
StoreFrontGlass01 Float 1

Platform

Fact Type Example Description
is_pc Float 1
is_console Float 0

Examples

// Somewhere in another file
::MyCampaignState <- {
	PathA <- false;
	PathB <- false;
}

function PickPathA() {
	::MyCampaignState.PathA = true;
	::MyCampaignState.PathB = false;
}

function PickPathB() {
	::MyCampaignState.PathA = false;
	::MyCampaignState.PathB = true;
}

// response_testbed_addon.nut

function IsPathA(query) { // query is IMPORTANT!!! DO NOT REMOVE!!!
	return ::MyCampaignState.PathA;
}

function IsPathB(query) { // query is IMPORTANT!!! DO NOT REMOVE!!!
	return ::MyCampaignState.PathB;
}

local MyCustomResponses = [
	//----------------------------------------
    // SUBJECT: PathA
    //----------------------------------------
    {
        name = "PathA_NamVet", // Doesnt matter what the name is, as its used for debugging.
        criteria = [
            ["concept", "TLK_REMARK"],
            g_rr.IsNamVet,
            ["subject", "PathA"],		// contextsubject
            ["distance", 0, 500], 		// min, max
			[ IsPathA ]					// returns true to say a line or false to stop saying a line
        ],
        responses = [
            { scenename = "scenes/NamVet/WorldFarmHouse0204.vcd" },  //Through here!
            { scenename = "scenes/NamVet/WorldHospital0327.vcd" },   //Through here! 
			{ scenename = "scenes/NamVet/FollowMe01.vcd" }, 		 //Come on, this way.
			{ scenename = "scenes/NamVet/FollowMe04.vcd" }, 		 //This way, people!
			{ scenename = "scenes/NamVet/FollowMe05.vcd" } 			 //This way.
        ],
        group_params = RGroupParams()
    },
    //----------------------------------------
    // SUBJECT: PathA_Blocked
    //----------------------------------------
    {
        name = "AlleyABlocked_NamVet",
        criteria = [
            ["concept", "TLK_REMARK"],
            g_rr.IsNamVet,
            ["subject", "PathA_Blocked"],	// contextsubject
            ["distance", 0, 500],			// min, max
			[ IsPathB ]						// returns true to say a line or false to stop saying a line
        ],
        responses = [
			{ scenename = "scenes/NamVet/WorldHospital0209.vcd" }, //Gotta find another way.
			{ scenename = "scenes/NamVet/WorldHospital0210.vcd" }, //This seemed too easy.
			{ scenename = "scenes/NamVet/WorldHospital0211.vcd" }  //Going to have to go top side.
        ],
        group_params = RGroupParams()
    },
]
rr_ProcessRules(MyCustomResponses); // This is where it creates the response rules for a map / campaign
Examples made in offical files but not used
g_ignoredecisionrules <- [
{
	name = "CoachSeeSmoker",
	criteria = [
		[ "concept", "onSeeEnemy" ], // arrays of two entries are considered to be fact = value
		[ "speaker", "coach" ],
		[ "numAllies", 1, 4 ],	// arrays of three entries are considered to be fact >= x && fact <= y
		[ "enemyType", "smoker" ],
		[ @(query) (query.GameTime) < 30 ] // arrays of one entry are expected to be functions
	],
	responses = [
		{ scenename = "coach_see_smoker_1.vcd", // if a 'scenename' key is present, this is expected to be a 'scene' response
		  soundlevel = 80,
		  onFinish = @(query, speaker) speaker.smokersSeen += 1 // expected to be a function
		} , {
		  func = ZombieFreakout // if a 'func' key is present, this is expected to be a 'do function' response 
		} , {
		  func = @(query,speaker) speaker.PointAt( query.enemy ) // anonymous functions are ok too
		} , {
		  scenename = "coach_see_smoker_2.vcd",
		  sndlevel = 90,
		  onFinish = @(speaker, query) speaker.smokersSeen += 1
		}
	],
	group_params = RGroupParams({ permitrepeats = false, sequential = true, norepeat = false })
},
{ // another rule to test that I don't inadvertently write state shared between rules
	name = "Dummy", 
	criteria = [
		[ "concept", "dummy" ], // arrays of two entries are considered to be fact = value
		[ "speaker", "zombie" ]
	],
	responses = [
		{ scenename = "zombie.vcd", // if a 'scenename' key is present, this is expected to be a 'scene' response
		  sndlevel = 80,
		  onFinish = @(speaker, query) speaker.smokersSeen += 1 // expected to be a function
		} 
	],
	group_params = RGroupParams( ) // default 
}
]
// if you want to share a criterion between many rules, you can declare it as its own variable like this:
CriterionIsNotCoughing <- [ "Coughing", 0 ]
CriterionIsc6m3_port  <- [ "map", "c6m3_port" ]  //use for the moveon example below to scope the rule change
CriterionIsAwardProtector  <- [ "awardname","Protector" ]
 
// and then include it into the criteria lists as per the examples below. The "g_rr." part
// is a bit of temporary cruft that will go away once we resolve some questions about scope resolution
// in map .nut files.

// here's a demo of a function that reads a context out of a character, and sets it again. (you can also read context out of the query.)
function DemoWritingContextToCharacter( speaker, query )
{
	// look up a 'bananas' context in the query, add one to it, and write it to the character. write 1 if query has no bananas.
	if ( "bananas" in query ) 
	{
		speaker.SetContext( "bananas", (query["bananas"] + 1).tostring(), 0 )
	}
	else
	{
		speaker.SetContext( "bananas", "1", 0 )
	}
}

function SetAwardSpeech	(speaker, query)
{
	local  ProtectedDude = rr_GetResponseTargets()[ query.subject ]
	ProtectedDude.SetContext( "AwardSpeech", query.who, 10 )
}


function SubjectAward ( query )
{
	if ( "AwardSpeech" in query )
	{ 
		if ( query["AwardSpeech"] == query["subject"] )
		{
			return true
		}
		else
		{
			return false
		}
	}
	else
	{
		return false	
	}
}


g_decisionrules <- [
{
	name = "PlayerMoveOnCoach",
	criteria = [
		[ "concept", "PlayerMoveOn" ], // conceptPlayerMoveOn
		g_rr.CriterionIsNotCoughing,
		[ "Who", "Coach" ],	// isCoach
		g_rr.CriterionIsc6m3_port
	],
	responses = [
		{ scenename = "scenes/Coach/MoveOn01.vcd",
		}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
},
{
	name = "PlayerMoveOnGambler",
	criteria = [
		[ "concept", "PlayerMoveOn" ], // conceptPlayerMoveOn
		g_rr.CriterionIsNotCoughing,
		[ "Who", "Gambler" ],	// isgambler
		g_rr.CriterionIsc6m3_port
	],
	responses = [
		{ scenename = "scenes/Gambler/MoveOn01.vcd",
			followup = RThen( "self",  "DemonstrateScriptFollowup", {additionalcontext="whatever"}, 1.23 ) // dispatches a "DemonstrateScriptFollowup" concept back to self
			func = DemoWritingContextToCharacter
		}
		//,{ scenename = "scenes/Gambler/MoveOn02.vcd", // another vcd response
		//}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
},
{	// this rule demonstrates how a followup can be an arbitrary script fucntion
	name = "TestBogusGambler",
	criteria = [
		[ "concept", "DemonstrateScriptFollowup" ],
		[ "Who", "Gambler" ],
		g_rr.CriterionIsc6m3_port
	],
	responses = [
		{ func = g_rr.DemoScriptFollowupFunction 	}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
},
{
	name = "PlayerMoveOnMechanic",
	criteria = [
		[ "concept", "PlayerMoveOn" ], // conceptPlayerMoveOn
		g_rr.CriterionIsNotCoughing,
		[ "Who", "Mechanic" ],	// isMechanic
		g_rr.CriterionIsc6m3_port
	],
	responses = [
		{ scenename = "scenes/Mechanic/MoveOn01.vcd"
		}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
},
{
	name = "PlayerMoveOnProducer",
	criteria = [
		[ "concept", "PlayerMoveOn" ], // conceptPlayerMoveOn
		g_rr.CriterionIsNotCoughing,
		[ "Who", "Producer" ],	// isgambler
		g_rr.CriterionIsc6m3_port
	],
	responses = [
		{ scenename = "scenes/Producer/MoveOn01.vcd"
		}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
},
{
	name = "PlayerAwardProtect",
	criteria = [
		[ "concept", "Award" ], // conceptAward
		g_rr.CriterionIsAwardProtector
	],
	responses = [
		{ 
			func = SetAwardSpeech	
		}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
},
{
	name = "ProtectedFriendlyFire",
	criteria = [
		[ "concept", "PlayerFriendlyFire" ], 
		[ SubjectAward ]
	],
	responses = [
		{ 
			func = DemoScriptFollowupFunction			
		}
	],
	group_params = RGroupParams({ permitrepeats = true, sequential = false, norepeat = false })
}
]

Advanced usage

Known limitations

See also