Aller au contenu

Photo

Speaker and listener system for chat-talking to objects and other things


  • Veuillez vous connecter pour répondre
33 réponses à ce sujet

#1
Tchos

Tchos
  • Members
  • 5 042 messages
I thought it might be better suited to this subforum rather than buried in my development diary to have this here, where any possible discussion (in the happy event that it inspires any) won't be lost in the volume of other matters. The following text is the same as in my original post.

Yesterday I was compelled to investigate something that I had found interesting earlier, which was the ability to use input that a player types into the chat window to make things happen in the game. This "listening" system wasn't clearly documented in any way I could see in the scripts and functions available in the toolset, and I couldn't find any explanations or documentation in a web search either. But I picked apart the Dungeon Master creature, which is where I had seen it in use, and read through its scripts, testing a few things through trial and error until I figured out what it was doing and how I could use it.

Something like this may be in use in some module somewhere, but I wouldn't know where to look to find it. The purpose I have in mind is to have a hidden thing remain hidden, with no trace, until the player is near it, and says a magic word.

So, for instance, there's a wall. The player finds out in the course of the game that there's a hidden passage there, but it is warded against detection. But the player can learn that the words "open sesame" will open the secret passage. The player approaches the wall, types "open sesame" into the chat window, and the wall opens.

Or, the player finds a strange model town on a table in an attic. Having heard of what could be done in such a situation, the player approaches the model town and speaks the words "Betelgeuse, Betelgeuse, Betelgeuse", and a strange undead NPC is summoned.

It seems to require some spawn in conditions, and since Ipoints don't have spawn scripts, I had to use a creature. I'm not even sure if it's possible for a placeable to "listen". So I dropped in an immortal, unresponsive creature with the "Invisible Man" appearance, and put it somewhere out of the way, and set up its necessary scripts.

I also involved a trigger, and a placeable for this setup. I called them the Trigger, the Listener, and the Responder. The only purpose of the trigger is to tell the Listener to start listening.

I could probably do without the trigger if I set up the Listener's perception script instead. For that matter, I don't strictly need the Responder, if I build its functions into the Listener, which I assume should be possible, but this is how I have it functioning now.

For this demo, I selected a stuffed head as the Responder, and painted the trigger around it. There is no indication of anything out of the ordinary in the game when you approach it, but if you type any of the words/phrases I put into the script while you're close enough to the head (inside the trigger), it'll respond. If you're outside of the trigger and speak the words, nothing happens.

In most cases, you probably wouldn't want it to actually speak responses like I'm having it do here just to demonstrate different things happening with different words. I imagine you'd want to cause things to happen silently, or with some floating flavour text or something. Here, as an action for when you speak the proper magic words, I just threw on a special effect, but of course you can make anything you want happen.


Modifié par Tchos, 04 février 2013 - 12:01 .


#2
Rolo Kipp

Rolo Kipp
  • Members
  • 2 790 messages
<pretending to listen...>

You might want to take a look at what the Layonara Team and The Magus did with their Ambiguous Interactive Dungeons (upgrade) v3.0.0. Though written for NwN1, a great deal of it should (pardon the pun) translate pretty painlessly :-)

<...to wise words>

#3
Dann-J

Dann-J
  • Members
  • 3 161 messages
Will hostile creatures 'listen' as well? I'm thinking of a guardian creature (like an iron golem) that starts off hostile, but stands down if you say the correct password (unless you *want* to bash it to death).

Or you could stop a gang of rampaging actors in their tracks by naming 'The Scottish Play', and watch them flee in terror.

If hostiles prefer to kill first and ask questions never, then a neutral guardian who becomes hostile only if you try to walk past it (unless you deactivated it first with the code word) would certainly be possible. Although for a non-hostile creature, it would be much easier to just give it a conversation.

#4
Tchos

Tchos
  • Members
  • 5 042 messages
Dann: The way I have it set up, the actual listener is always a separate, invisible creature, so if you use this same setup, the hostile creature would be the Responder, not the Listener. The Responder could be hostile or friendly.

But I'm testing it now with a hostile Listener.

Okay, that was pretty amusing. I made the Listener hostile, and went into the area to test it. One of my other unrelated NPCs standing by was attempting to fight the Listener, despite it having the Invisible Man appearance. I would have to add some other properties to make it so that other NPCs wouldn't be able to detect it. Nevertheless, the magic words still worked, even as the bystander NPC was kicking wildly at the empty air. So yes, a hostile creature can still listen.

I like that Macbeth idea! It would have to only work inside a theatre, to be most accurate to the superstition.

Rolo: That probably would have been most useful if I had found it before yesterday, because I currently have it working fine for the purpose I had in mind. But it'll probably be enlightening to see some other methods they may have used (I note that the upgraded version says a listener is no longer required), and see what additional features they've built in.

Modifié par Tchos, 04 février 2013 - 12:39 .


#5
henesua

henesua
  • Members
  • 3 863 messages
Does NWN2 not have a Chat event?

#6
Tchos

Tchos
  • Members
  • 5 042 messages
Hmm, it does have an "OnChat" event in the Module Properties. By default, the script in that slot is "n2_mod_chat", but that script does not exist in the toolset.

#7
henesua

henesua
  • Members
  • 3 863 messages
The OnChat event is the best place to parse chat, and determine what to do with it.

Listeners work but are quirky.

#8
Tchos

Tchos
  • Members
  • 5 042 messages
What are some known quirks, and do you know where there are any examples of parsing chat in the OnChat event?

#9
Morbane

Morbane
  • Members
  • 1 883 messages
just a factoid: the invisible man appearance is bugged - not sure how but I avoid it unless it is attacking as an actual hostile.

#10
painofdungeoneternal

painofdungeoneternal
  • Members
  • 1 799 messages
I've done heavy usage of the onchat event in CSL

I have lots of chat commands, many of which are very useful for development ( dm_showinfo command listing all variables and properties of a targeted item for example ) Also have it so if you are silenced, and you use chat, it blocks your being able to speak, but that only matters if MP or if you talk using chat for your characters in single player. ( even did commands to list, remove and add spells to a targeted creature.

I even did a thing to force what is said to translate to pirate and shakespeare, in addition to recreating the DMFI languages.

Putting a hook on this event, either per area, or if a user enters a trigger, or just if a var is set on the player. Your ideas are great, but the overhead of a NPC listener, and bugs involved, well you really should focus on something which is pretty rock solid and easier on the server/game.

Modifié par painofdungeoneternal, 04 février 2013 - 02:06 .


#11
Tchos

Tchos
  • Members
  • 5 042 messages
I'm all for a more elegant solution, though I'd really like to know what exactly these bugs are that are being mentioned here, and under what circumstances I might see them.

Understand, that I did it this way because the Dungeon Master creature did it this way, and it was the only example I could find of such a thing being done in the game (I wasn't aware of the other solutions, especially of the very useful-sounding CSL solution, which happens to include something I was asking for in a previous diary post). Unfortunately, searching for things like "chat" and "listener" didn't give me any useful results at the time.

#12
Dann-J

Dann-J
  • Members
  • 3 161 messages
One annoying bug with the 'invisible man' appearance is that you can't select it in the toolset area window once you plonk it down. You have to select it via the creature list to do anything to it. The air elemental appearance is less buggy, but even if you remove its VFX I think it still makes an air elemental noise.

If you want a hostile creature to be ignored, the best way is to give it an OnSpawn script that sets it to ethereal. Although I've found that true-seeing creatures can detect even ethereal creatures.

I wonder what the processing overheads are having a large group of creatures or NPCs actively listening? It might be fine for a lone creature, but could be potentially laggy if you're hoping to stop a horde of orcs with a special orcish command.

I have a group of bards in an upcoming module, and I think it'd be a nice touch if they thanked you for clapping at the end of a performance. Are the emoting animations/sounds considered a listenable event, by any chance?

#13
painofdungeoneternal

painofdungeoneternal
  • Members
  • 1 799 messages

Tchos wrote...

I'm all for a more elegant solution, though I'd really like to know what exactly these bugs are that are being mentioned here, and under what circumstances I might see them.

Understand, that I did it this way because the Dungeon Master creature did it this way, and it was the only example I could find of such a thing being done in the game (I wasn't aware of the other solutions, especially of the very useful-sounding CSL solution, which happens to include something I was asking for in a previous diary post). Unfortunately, searching for things like "chat" and "listener" didn't give me any useful results at the time.


OnChat was added in 1.23, so before that, there was no other way. And it was done very well unlike much older features of the game ( Grinning Fool was a very good progammer )

Also it requires adding events to the module, so it's not a good option for regular content which is supposed to work with existing modules. ( if 1.24 ever got released I could have dynamically set the events )

However, its much like the dialog scripts, what is said, and the actors are passed as parameters, and all the "work" is visible in your code ( so you can exit out early except when it's needed ). A listener is using parts of the engine you can't see, and there is the overhead of it figuring out who hears what ( distance calculations, is it in the same area, how many creatures are in module ), and then it hits your code.

Modifié par painofdungeoneternal, 04 février 2013 - 04:23 .


#14
Tchos

Tchos
  • Members
  • 5 042 messages
Dann: Correct that you can't select the invisible man except through the creature list. That's how I was doing it, though I also temporarily set its appearance to other things while I was working on it. I don't really see that as a bug, though.

For the horde of orcs you describe, there wouldn't be any need to have all of them listening. I would have a single listener that causes the changes in all of the orcs.

I think the emotes are events that can be detected with listening or OnChat, but I haven't looked into it.

Pain: Are you saying that listeners are using system resources even when I have listening explicitly turned off via the SetListening(oListener, FALSE) command?  I turn it on only on entering the trigger, and I turn it off again on exiting the trigger.

Modifié par Tchos, 04 février 2013 - 04:29 .


#15
kevL

kevL
  • Members
  • 4 056 messages
btw Tchos, you might try a scripthidden Listener, with its AI flagged True

#16
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
Pain: Could you do a quick explanation of how to get the on-chat event working? I've tried working with the csl scripts, but any script with the _scinclude_chat crashes the game. I can get listening patterns to work, but they're very limited in regards to distance (like 20m for a shout), and it's difficult to do any thing complicated without an actual string to parse.

#17
Tchos

Tchos
  • Members
  • 5 042 messages
I asked pretty much the same question of Pain, and I'll paste what he told me, for his convenience.  I'm going to experiment with this later, too, but I'll report on the result of a scripthidden Listener first.  Good idea, kevL!

painofdungeoneternal wrote...
Just the CSL library folder is the main thing needed, and its completely safe since even if installed, it is just includes and not compiled scripts.

Each other folder is to support a specific feature, ie there is an AI folder, chat folder, spells folder.

Then you need the support files folder - ie some of the utp and 2da files are really critical for the various scripts. Some can be avoided, but i've not touched this in about 12 months and if you start cherry picking everything too much you likely will have things not work. Again csl prefix should prevent any conflicts. There are 2da's to configure preferences, languages ( even did a language editor too )

Finally you need the onchat event, which is in the events folder, you don't need the other events. Probably look at the bare events folder. This needs to be hooked up to your module.

After that the CSLChat folder has all the chat commands I've done. You can just drag in which ever ones you want, you type the command, it is the file name. They are done just like item scripts so you can easily add new ones.



#18
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
Thanks, that should be enough to get me started. It seems like the on-chat event is actually a conditional script, which must have been what threw me off before.

#19
Tchos

Tchos
  • Members
  • 5 042 messages
As a followup to your suggestion, kevL, setting the Listener to script hidden, with its AI set to remain on when hidden, did not allow it to work.

#20
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
Ok, using the OnChat event turns out to be really simple. A bare bones script is as follows:

int StartingConditional(object oSender, object oTarget, int nChannel, string sMessage)
{
int iShowMessage = FALSE;

return iShowMessage;
}

Returning TRUE passes the chat message on, FALSE blocks it.

The first thing I noticed was how the default AI shouts to itself constantly, creatures calling in their allies to attack their last attacker. This creates a lot of extra events for the AI to work with, so it either bogs things down or makes combat flow a little bit more naturally. Either way, it might be interesting to tweak those listening patterns and shouts to create different sorts of combat flows.

Another idea would be for handling ambient NPCs - not just the PC yelling at crowds, but NPCs shouting to other NPCs, spontaneously grouping themselves into clusters; passersby stopping to gawk at market stalls, friends meeting to chat, spontaneous queue formation.

#21
Tchos

Tchos
  • Members
  • 5 042 messages
Do you know if that's related to the ability to set an NPC's spawn script to "use ambient", which causes any two such NPCs to wander around, but more importantly to approach each other if they pass nearby, wave or bow to each other, and start doing a talking animation, before going their separate ways again? That sounds like what you're talking about with friends meeting to chat.

#22
kevL

kevL
  • Members
  • 4 056 messages

Tchos wrote...

As a followup to your suggestion, kevL, setting the Listener to script hidden, with its AI set to remain on when hidden, did not allow it to work.

bah confirmed. sry T.

I did several tests and determined that DisableAI, while scripthidden, turns on and off the heartbeat script at least. But the onDialog event is unaffected and always off when hidden.


Do you know if that's related to the ability to set an NPC's spawn script to "use ambient", which causes any two such NPCs to wander around, but more importantly to approach each other if they pass nearby, wave or bow to each other, and start doing a talking animation, before going their separate ways again? That sounds like what you're talking about with friends meeting to chat.

some time ago i investigated getting ambient animations going, and they're handled in x0_i0_anims but the default onDialog script makes no reference to it/them -- so i'd say nope.

what Lugaid's talking about sounds much cooler,

#23
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
Right now, I'm thinking for a simple system to have three basic shouts - "Look at me", "Come to me" and "Go away". Some ambient NPCs would be set to shout those things randomly, and the other ambient NPCs would be set to listen for them. So an NPC could walk down a crowded street, shout "Look at me", and some other NPCs would turn, some would just look left or right, and some might actually wave. "Come to me" would cause the NPCs to run/walk up to them and start using various talk animations. "Go away" would cause them to flee, like a cop clearing out loiterers or an intimidating character clearing a path through a crowd.

It might also be interesting to have the PC randomly issue the shouts, or maybe as they pass certain triggers. Maybe rig up the player-on-equip event to scatter the civilians everytime the player draws a sword.

#24
kevL

kevL
  • Members
  • 4 056 messages
wow, sounds like pinball !

i Believe it should be an extension of, or compatible with kam_'s Commoner AI ...

#25
Lugaid of the Red Stripes

Lugaid of the Red Stripes
  • Members
  • 955 messages
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.