Here's what I got working.
On the on-spawn script for all listening creatures:
[nwscript]//Sets listen patters for ambient AI use (11-14)
void SetAmbientListeningPatterns(object oListener) {
SetListenPattern(oListener, "AMBCHAT_LOOKATME", 11);
SetListenPattern(oListener, "AMBCHAT_COMEHERE", 12);
SetListenPattern(oListener, "AMBCHAT_GOAWAY", 13);
SetListenPattern(oListener, "AMBCHAT_FOLLOW", 14);
SetListening(oListener, TRUE);
}
[/nwscript]
inserted into the on-conversation script:
[nwscript] int nMatch = GetListenPatternNumber();
object oShouter = GetLastSpeaker();
object oIntruder;
int iFocused = SCGetIsFocused(oCharacter);
//AmbientAdditions
if ((GetLocalInt(OBJECT_SELF, "iAmbient") > 0) && (GetLocalInt(OBJECT_SELF, "iAmbientOverrideStep") == 0)) {
if (nMatch == 11) {//Look at me
SetLocalInt(OBJECT_SELF, "iAmbientOverrideStep", 99);
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "lookatme");
SetLocalObject(OBJECT_SELF, "oAmbientOverrideTarget", oShouter);
ExecuteScript("ambienttasks", OBJECT_SELF);
}
else if (nMatch == 12) {//Come here
SetLocalInt(OBJECT_SELF, "iAmbientOverrideStep", 99);
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "comehere");
SetLocalObject(OBJECT_SELF, "oAmbientOverrideTarget", oShouter);
ExecuteScript("ambienttasks", OBJECT_SELF);
}
else if (nMatch == 13) {//Go away
SetLocalInt(OBJECT_SELF, "iAmbientOverrideStep", 99);
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "goaway");
SetLocalObject(OBJECT_SELF, "oAmbientOverrideTarget", oShouter);
ExecuteScript("ambienttasks", OBJECT_SELF);
}
else if (nMatch == 14) {//Follow
SetLocalInt(OBJECT_SELF, "iAmbientOverrideStep", 99);
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "follow");
SetLocalObject(OBJECT_SELF, "oAmbientOverrideTarget", oShouter);
ExecuteScript("ambienttasks", OBJECT_SELF);
}
}
[/nwscript]
Nested within in my heartbeat-based ambient system I have the following function (it calls other functions within the script):
[nwscript]//Handles override ambient tasks
void AmbientOverride(string sPostTag) {
//SendMessageToPC(GetFirstPC(), "Doing Ambient Override");
int iStep = GetLocalInt(OBJECT_SELF, "iAmbientOverrideStep");
string sTask = GetLocalString(OBJECT_SELF, "sAmbientOverrideTask");
object oTarget = GetLocalObject(OBJECT_SELF, "oAmbientOverrideTarget");
if (iStep == 99) {//initialize
if (sTask == "lookatme") {
float fFace = fBearingToLocation(OBJECT_SELF, GetLocation(oTarget));
int iRandom = d4();
if (iRandom == 1) {
SetFacing(fFace);
iStep = d6();
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "watch");
}
else if (iRandom == 2) {
SetFacing(fFace);
PlayCustomAnimation(OBJECT_SELF, "wave", FALSE);
iStep = d6();
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "watch");
}
else if (iRandom == 3) {
SetFacing(fFace);
PlayCustomAnimation(OBJECT_SELF, "waveshort", FALSE);
}
else if (iRandom == 4) {
float fTurn = fFace - GetFacing(OBJECT_SELF);
if (fTurn < -360.0) {fTurn = fTurn + 360.0;}
if (fTurn > 360.0) {fTurn = fTurn - 360.0;}
if (fTurn < 0.0) {PlayCustomAnimation(OBJECT_SELF, "lookleft", FALSE);}
else {PlayCustomAnimation(OBJECT_SELF, "lookright", FALSE);}
}
}
if (sTask == "comehere") {
int iRandom = d2();
if (iRandom == 1) {//refuse
float fFace = fBearingToLocation(OBJECT_SELF, GetLocation(oTarget));
SetFacing(fFace);
PlayCustomAnimation(OBJECT_SELF, "nodno", FALSE);
}
else {//mingle
float fDist = GetDistanceBetween(OBJECT_SELF, oTarget);
if (fDist > 15.0) {
ClearAllActions();
DelayCommand(0.1, ActionMoveToObject(oTarget, TRUE, 1.5));
}
else {
Mingle(sPostTag, 0, oTarget);
}
iStep = d12();
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "mingle");
}
}
if (sTask == "goaway") {
int iRandom = d4();
if (iRandom == 1) {//refuse
float fFace = fBearingToLocation(OBJECT_SELF, GetLocation(oTarget));
SetFacing(fFace);
PlayCustomAnimation(OBJECT_SELF, "nodno", FALSE);
}
else if (iRandom == 2) {//run
ClearAllActions();
DelayCommand(0.1, ActionMoveAwayFromObject(oTarget, TRUE));
iStep = d6();
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "flee");
}
else if (iRandom == 3) {//walk
ClearAllActions();
DelayCommand(0.1, ActionMoveAwayFromObject(oTarget, FALSE));
iStep = d12();
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "flee");
}
else {//Point
float fFace = fBearingToLocation(OBJECT_SELF, GetLocation(oTarget));
SetFacing(fFace);
PlayCustomAnimation(OBJECT_SELF, "point", FALSE);
}
}
if (sTask == "follow") {
DelayCommand(1.0, SpeakString("AMBCHAT_FOLLOW", TALKVOLUME_SILENT_SHOUT));
float fDist = GetDistanceBetween(OBJECT_SELF, oTarget);
if (fDist > 15.0) {
ClearAllActions();
DelayCommand(0.1, ActionMoveToObject(oTarget, TRUE, 1.5));
}
iStep = d12() + 6;
SetLocalString(OBJECT_SELF, "sAmbientOverrideTask", "follow");
}
if (iStep == 99) {iStep = 0;}//reset step to 0 if not otherwise reset
SetLocalInt(OBJECT_SELF, "iAmbientOverrideStep", iStep);
}
else {
if (sTask == "watch") {Watch(sPostTag, 0, oTarget);}
if (sTask == "mingle") {Mingle(sPostTag, 0, oTarget);}
if (sTask == "flee") {Flee(sPostTag, 0, oTarget);}
if (sTask == "follow") {Follow(sPostTag, 0, oTarget);
}
iStep = iStep - 1;
if (iStep < 0) {iStep = 0;}
SetLocalInt(OBJECT_SELF, "iAmbientOverrideStep", iStep);
}
}
[/nwscript]
Then, I have the following called from the heartbeat of my shouters:
[nwscript]//Performs Ambient Chat Shout
void AmbientShout() {
int iRandom = d4();
if (iRandom == 1) {
SpeakString("AMBCHAT_LOOKATME", TALKVOLUME_SILENT_SHOUT);
PlayCustomAnimation(OBJECT_SELF, "waveshort", 0);
}
else if (iRandom == 2) {
DelayCommand(3.0, SpeakString("AMBCHAT_LOOKATME", TALKVOLUME_SILENT_SHOUT));
}
else if (iRandom == 3) {
SpeakString("AMBCHAT_COMEHERE", TALKVOLUME_SILENT_SHOUT);
PlayCustomAnimation(OBJECT_SELF, "wave", 0);
}
} [/nwscript]
I also set intimidating characters (a military patrol) to shout the 'go away' message.
For testing and fun I put the following into the module on-chat:
[nwscript]int StartingConditional(object oSender, object oTarget, int nChannel, string sMessage)
{
int iShowMessage = TRUE;
object oPC = GetFirstPC();
if (oSender == oPC) {
string sFeedback = "";
if (sMessage == "lookatme") {
AssignCommand(oPC, SpeakString("AMBCHAT_LOOKATME", TALKVOLUME_SILENT_SHOUT));
sFeedback = "Everyone, look at me!";
}
if (sMessage == "comehere") {
AssignCommand(oPC, SpeakString("AMBCHAT_COMEHERE", TALKVOLUME_SILENT_SHOUT));
sFeedback = "Everyone, come here!";
}
if (sMessage == "goaway") {
AssignCommand(oPC, SpeakString("AMBCHAT_GOAWAY", TALKVOLUME_SILENT_SHOUT));
sFeedback = "Everyone, go away!";
}
if (sMessage == "follow") {
AssignCommand(oPC, SpeakString("AMBCHAT_FOLLOW", TALKVOLUME_SILENT_SHOUT));
sFeedback = "Everyone, follow me!";
}
if (sFeedback != "") {
iShowMessage = FALSE;
SendMessageToPC(oPC, sFeedback);
}
}
return iShowMessage;
}[/nwscript]
In testing, it seems to make ambient NPCs into a bunch of slackers and gossips. In practice, I might tie alot of the shouts to triggers. For example, a town-walking NPC could walk past a market stall, and a trigger makes her shout at the vender (and other patrons) to turn and look at her. Maybe then the vendor shouts for her to come over and look at the merchandise.
I could imagine for PCs, you could rig up an impromtu street performance by gathering a crowd of passersby.