Author Topic: Generic NPC Conversation Scripting  (Read 760 times)

Legacy_MagicalMaster

  • Hero Member
  • *****
  • Posts: 2712
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #15 on: March 29, 2014, 02:05:37 am »


               

Last post on this subject in this thread given Henesua's comment (though many modules do give stat boosts to weapons if you weren't aware, Henesua, if you're trying to ultimately make this publicly available).  Meaglyn, feel free to make another thread or PM me if you want.


 




Um, you missed my entire point. I got your point. In NWN sheathing it _is_ unequipping it.




 


Unequipping it is putting into the same magical scaling backpack as your other items rather than sheathing it, there is zero reason to think otherwise.  Nor does your point about quickslots hold as you can (and most often do) quickslot several sets of weapons -- and there's no way you have a greataxe, greatsword, AND heavy flail all sheathed (but you could easily have those and more quickslotted).  The magic inventory is an example of Gameplay and Story Segregation.


 




And my quote about not taking away the walking stick addressed the issue of staves and spell slots.




 


I took it to mean the reverse -- that precisely because of that scene that people WOULD take away a stave.  And I have actually seen persistent worlds that DO expect you to unequip a staff while going into a town because they consider it a weapon.  So the issue definitely exists even if you have not personally experienced it.



               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #16 on: March 29, 2014, 02:35:38 am »


               

Last post on this subject in this thread given Henesua's comment (though many modules do give stat boosts to weapons if you weren't aware, Henesua, if you're trying to ultimately make this publicly available).


Don't sweat it, MM. My comment was to let you know that this conversation project is for a specific module in which I have anticipated these kinds of concerns, and so it won't be a problem.
 

I can probably cook up a patch easily enough if you want it.


I am already coding it. '<img'> I have modified NESS significantly so I just maintain it for myself at this point.
 

It's not an AI or anything. The multipage stuff is for cases where the PC has asked the NPC to tell him something and the NPC has agreed and is telling.


I was just curious as to how you set this up. If the string for a particular topic is beyond a certain length do you break it down into sections?

Also, how do you indicate whether an NPC has some long tale to tell on a particular subject? Thats what I was getting at with the pairing of PC Response with the NPC's dialog text.

Example lets say you want to indicate that NPC Foo has backstory to tell for Quest #A13Z. How do you initialize the NPC so that when in conversation with the PC the possibility for telling the backstory shows up? Is it simply the fact that you keep it to one quest at a time on an NPC so that if they have a long tale to tell on something it will be about the currently active quest?

With my approach I was thinking more of having a number of bits of information floating about amongst each "group" and each of these tied to a particular "keyword". Each NPC would have a couple of these, but not all. I'm thinking of flagging the NPC with the keyword. Alternatively rather than set keyword flags, I could populate a pseudo array of possible topics on the NPC and as long as this is populated the NPC can talk about them.
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #17 on: March 29, 2014, 02:05:36 pm »


               

Why every discussion with you MM turns into, "what I've said, what you've said, what I meant, what you meant, what word have you used, where you have been wrong" ?



               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #18 on: March 29, 2014, 05:33:14 pm »


               


Also, how do you indicate whether an NPC has some long tale to tell on a particular subject? Thats what I was getting at with the pairing of PC Response with the NPC's dialog text.


Example lets say you want to indicate that NPC Foo has backstory to tell for Quest #A13Z. How do you initialize the NPC so that when in conversation with the PC the possibility for telling the backstory shows up? Is it simply the fact that you keep it to one quest at a time on an NPC so that if they have a long tale to tell on something it will be about the currently active quest.




 



I broke it down into different types of information.  Back story is usually that NPC's story. It is triggered by a random "what do you do around here?, what brings you here?" sort of question. But the NPC could go in any direction you wanted from that starting point. 


 


Then there is what I call flavor text. This is fully controlled, you must specify the PC line and then at least on page of NPC output. Whether this shows or not is controlled by a prerequisite variable which, if configured, must be set non-zero on the module or PC for the text to fire. This text could be anything the story requires since you have control over the PC line.  These are used for things that aren't required to advance quests. Flavor text is usually once per NPC per PC.


 


The quests are in two parts really. There's the quest giver's part. This is where the quest is explained, requirements laid out etc and returning to complete the quest.  The former is provided from the generic "anything going on around here?" prompt in the main page, which also delivers rumors if no quest is available. The PC text for the later is usually defined as part of the quest, along with what the NPC says after the PC reports completion, but if not there are a few generic ones like "It is done."  That's the first part.


 


The second part is what I call quest info. This is configured the same way as quests (an ordered list of quest IDs). On dlg init the NPC is given the quest info for the first quest listed for which the PC is in the right state. What quest state is required, along with any item(s) needed to be


the PC's possession etc are part of the configuration for that quest info segment. The text the PC speaks for this is required to be provided.


It can be anything appropriate for the quest.  And then what the NPC replies with is a possibly multipage response (along with optionally taking the items, given other items etc).  It can also be configured to require a bribe or intimidation/persuade to get the NPC to give the quest info.


Usually delivering the quest info advances the quest state, but that is not required.


 


All of these (flavor, quest assignment and completion, and quest info anyway) allow for an arbitrary script to be specified as well. Together all of these give a fair amount of flexibility.  There are few other things like support for doing the work before being assigned the quest and saying, "oh yeah, those goblins, I already took care of that.".  Opening stores, opening special stores base on race, class etc, stealing from the store, doing the subdlg I mentioned earlier.


 


Anyway, that's pretty much where I am now. I was aiming for something I could drop in place and have it more or less just work and be easily configurable outside of the toolset. This discussion has given me a few things to add and clean up as well.  It never ends '<img'> 


               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #19 on: March 30, 2014, 11:39:33 pm »


               I've found a minor annoyance that I'd like to remove.

I have enabled conversations to happen in different languages. This means that eavesdropping PCs should not necessarily understand what an NPC says to the PC they are conversing with.

My approach then is to make the conversation private. Only the PC speaking with the NPC hears the text in the conversation. And then I take all of the text in the conversation and put it through a language cypher. With SpeakString(), the NPC publicly says the garbled version of the text, and nearby PCs who understand them receive the translation in a message.

The problem is that to the PC speaking with them the NPC appears to be speaking their lines twice. How do I exclude the original PC from hearing both?

Perhaps there is a better way to do this? Or perhaps since roleplaying between PCs is really the focus, I should just keep all of this private between the NPC and the PC? Why I don't like this later option is that I am enabling NPCs to be followers, mentors etc... of PCs. And so it makes sense that if one PC hears another abusing one of their wards or even luring the NPC to another cause, that the eavesdropping PC could step up to intervene.
               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #20 on: March 31, 2014, 07:25:13 pm »


               

@henesua, I'm just looking at adding some of the language stuff myself. How do you make the NPC's conversation private? Maybe I missed it somewhere. ActionStartConversation takes a private argument, but what about the one that runs on the NPC directly?


The zdlg conversation file is started that way usually.



               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #21 on: March 31, 2014, 08:04:02 pm »


               

Z-dialog uses ActionStartConversation() through its wrapper function:



StartDlg( oSpeaksWith, oCreature, sDialog, bPrivate, bPlayHello, bZoom);

I set bPrivate to TRUE, to make a conversation using z-dialog private.


 


I have my own wrapper function for NPC conversation which sorts out the parameters for StartDlg() along with a variety of other things, and it is launched by the OnConversation creature AI event. So thats what gets used when a PC clicks on an NPC with the "Talk" action.


 


As to how one toggles a conversation's privacy on the creature blueprint, I do not know. Does this exist?

 

As to BeginConversation(). I suggest not using that function unless you have to because it lacks all of the settings which ActionStartConversation() has.


               
               

               
            

Legacy_meaglyn

  • Hero Member
  • *****
  • Posts: 1451
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #22 on: March 31, 2014, 08:45:06 pm »


               


I have my own wrapper function for NPC conversation which sorts out the parameters for StartDlg() along with a variety of other things, and it is launched by the OnConversation creature AI event. So thats what gets used when a PC clicks on an NPC with the "Talk" action.


 


As to how one toggles a conversation's privacy on the creature blueprint, I do not know. Does this exist?



 


Yeah, i know about the wrapper, wasn't sure about the other other part. That makes sense. Don't configure a conversation and let the on conversation event handle it all in scripting.  I may have to look into that... That would make it easier to have NPCs who don't like the PC brush him/her off with a one-liner too.  Thanks.


               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #23 on: April 06, 2014, 08:39:04 pm »


               

I was working on a portion where the PC can frighten the NPC and found a problem with the default state script nw_g0_fear.


 


nw_g0_fear only forces the frightened creature to run away from enemies rather than the person who applies the frighten effect to the subject. One potential solution to this would be to find the creator of the frightened/fear effect, but I wasn't sure if this would eat up too many resources on each heartbeat. So instead I created a local integer flag to check for.



int GetCreatureIsAfraidOfTarget(object oCreature, object oTarget)
{
    return GetLocalInt(oCreature, "AI_AFRAID_"+ObjectToString(oCreature));
}

The downside of this is that I have to delete the flag with a delay if the fear has a particular duration. Say a Delay of 30 seconds.


 


So which is worse? Having a 30 second delay or once a heartbeat iterating through all the affects on an NPC looking for the creature which caused the fear effect in the first place?



               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #24 on: April 07, 2014, 08:03:16 am »


               


I was working on a portion where the PC can frighten the NPC and found a problem with the default state script nw_g0_fear.


 


nw_g0_fear only forces the frightened creature to run away from enemies rather than the person who applies the frighten effect to the subject. One potential solution to this would be to find the creator of the frightened/fear effect, but I wasn't sure if this would eat up too many resources on each heartbeat.




Absolutely fine approach. The heartbeat is already fires and such small addition doesnt make a difference neither in long term run. Local is nice idea, but would have to be initiated somewhere - fear spell or in a first time nw_g0_fear is called. I dont think its worth it because the NPC might be target of different fear effect. Not sure whether she can be a target of a multiple fear effects but if she can, this could be also taken into consideration.


 


Anyway, are you sure is it wise idea? It can be exploited - the creature cant do anything so different player in multiplayer environment might go there and kill her - she wont run from him. Same for a player - if he wont run from the enemies then those who hasnt frightened him can kill him with easy if he will just stand there.


 



 


So which is worse? Having a 30 second delay or once a heartbeat iterating through all the affects on an NPC looking for the creature which caused the fear effect in the first place?



Neither. The concerns on both mechanism that are spreading on these forums are overexaggerated.


               
               

               
            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #25 on: April 07, 2014, 09:48:10 am »


               


I was working on a portion where the PC can frighten the NPC and found a problem with the default state script nw_g0_fear.


 


nw_g0_fear only forces the frightened creature to run away from enemies rather than the person who applies the frighten effect to the subject. One potential solution to this would be to find the creator of the frightened/fear effect, but I wasn't sure if this would eat up too many resources on each heartbeat. So instead I created a local integer flag to check for.



int GetCreatureIsAfraidOfTarget(object oCreature, object oTarget)
{
    return GetLocalInt(oCreature, "AI_AFRAID_"+ObjectToString(oCreature));
}

The downside of this is that I have to delete the flag with a delay if the fear has a particular duration. Say a Delay of 30 seconds.


 


So which is worse? Having a 30 second delay or once a heartbeat iterating through all the affects on an NPC looking for the creature which caused the fear effect in the first place?




 


What about doing something like this...


void main()
{
    int nFear = GetHasEffect(EFFECT_TYPE_FRIGHTENED);
    object oCreator = GetEffectCreator(nFear);
 
    SendForHelp();
    //Allow the target to recieve commands for the round
    SetCommandable(TRUE);
 
    ClearAllActions();
    int nCnt = 1;
 
    //Get the nearest creature to the affected creature
    object oTarget = GetNearestObject(OBJECT_TYPE_CREATURE, OBJECT_SELF, nCnt);
    float fDistance = GetDistanceBetween(OBJECT_SELF, oTarget);
 
    while (GetIsObjectValid(oTarget) && fDistance < 5.0)
    {
        if (oTarget == oCreator)
        {
            //Run away if they are the source of the fear effect
            ActionMoveAwayFromObject(oTarget, TRUE);
            break;
        }
 
        //If not an enemy interate and find the next target
        nCnt++;
        oTarget = GetNearestObject(OBJECT_TYPE_CREATURE, OBJECT_SELF, nCnt);
    }
 
    //Disable the ability to recieve commands.
    SetCommandable(FALSE);
}

               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #26 on: April 07, 2014, 03:48:52 pm »


               

To answer both of you let me clarify what I did:



if(     fDistance <= 5.0
    &&(     GetIsEnemy(oTarget)
        ||  GetCreatureIsAfraidOfTarget(OBJECT_SELF,oTarget)
      )
  )
{
    //Run away
    ActionMoveAwayFromObject(oTarget, TRUE);
    break;
}

I kept the behavior more or less the same, but in addition to commanding creatures to flee from enemies they also flee from anyone they are afraid of. The token check I mentioned earlier is wrapped in that function, GetCreatureIsAfraidOfTarget(), which allows me to change the method of establishing whether an NPC is afraid someday in the future. At present its just a token, but I can incorporate a graduated "fear" scale if I want rather than a binary flag.


 


I went with this rather than searching for the creator for a few reasons.


  1. it is more efficient than a loop through all effects on the creature. (Aside: I don't believe what you posted works, Pstemarie, because the return value of GetHasEffect() is only a 0 or a 1 as far as I know. It certainly isn't the special struct, "effect", which I believe is what is needed in GetEffectCreator().)

  2.    
  3. I was thinking that when a creature is afraid, it flees from anything that is a threat. Fear is not necessarily so specific as to be limited to a single target, but instead I am thinking of fear as an overwhelming emotion which dominates one's outlook and response to any threat.

  4.    
  5. In the conversation I track a variable labeled "nerve" on the NPC. When the PC reduces the NPC's nerve to 0, they are very easily frightened. Another thing I want to do is test "nerve" in combat - especially if an NPC becomes a henchman. When nerve breaks in a combat situation they would flee. Basically I could have simply called this "morale" to use D&D terms, but I didn't think of that when I set this up.


               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #27 on: April 08, 2014, 03:34:19 pm »


               

I just laid the ground work for religious conversion and am amazed at how complex something like this can get.


 


I had to consider:


  • whether one religion considers another an enemy

  • whether the conversion is though intimidation (conversion by the sword) or persuasion, and have not yet worked out how bluffing will work

  • whether the NPC is a devoted follower of a religion, a shallow follower of a religion, or undevoted to any religion

  • whether the NPC and PC are already in similar faiths, but not exactly the same

... and so on.


 


A bit much. All of this is due to the fact that I allow one religion to be a parent of another, and I have introduced whether religions can be enemies. This is still only a caricature of reality, but I realize it is not worth adding any complexity further because the whole thing could collapse under its own weight.



               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #28 on: April 20, 2014, 05:30:39 pm »


               

I have been plugging away at this z-dialog script off and on for almost a month now. Getting close to usable for the module I am working on. I updated the first post in this thread with a link to my zdlg script on pastebin.

 

Yesterday I actually had a little bit of time to work on this uninterrupted and got two conversation options working:


  • [Ask for information]

  • [Make small talk]

I'm also working on merchant and house keeper sections of the dialog. Merchants are done. The house keeper is coming along. The way these work is that if an NPC is flagged with either of these roles, this section of the dialog starts at the beginning of a conversation, but a PC has the option to drop out of them to proceed with generic conversation.


 


 


[Ask for information]

has one core function which returns generic information about the area. at present this is limited to the name and location of a merchant, and a nearby innkeeper. those are things i typically ask for when travelling so i figured it made sense to allow the "people on the street" to supply the same information.

 

I am a little proud of a loop within my function, string DlgGetInformationOverview(object oPC); This is not fancy, and seems to work, but if others have a better way of finding areas connected to the current area I'd like to hear it. Basically I am looping through all objects in the area and looking for transition targets to a different area than this one. If I find an area that contains housing, the NPC tells the PC the direction to the transition to the housing.



        // we are not in "housing", lets look for Public Housing in an adjacent area

        object oHouse, oDest, oDestArea;


        object oTrans   = GetFirstObjectInArea(oArea);
        while(GetIsObjectValid(oTrans))
        {
            oDest    = GetTransitionTarget(oTrans);
            if(oDest!=OBJECT_INVALID)
            {
                oDestArea   = GetArea(oDest);
                if(oDestArea!=oArea)
                {
                    sHouseTag = GetLocalString(oDestArea,"HOUSE");
                    if(sHouseTag!="")
                    {
                        oHouse = GetWaypointByTag(sHouseTag);
                        if(GetLocalInt(oHouse,"HOUSE_PRIVATE"))
                            oHouse  = OBJECT_INVALID;
                        else
                            break;
                    }
                }
            }
            oTrans  = GetNextObjectInArea(oArea);
        }
        if( oHouse!=OBJECT_INVALID )
        {
            sOverview   += GetName(oHouse) +" "+ DlgGetLocationDescription(oDest) + "offers lodging.";
        }

[Make small talk]

First checks both the bluff skill and the persuade skill against a DC. If full or partial success is achieved, a random selection from a list of "small talk" dialogs set on the NPC in local strings is spoken. Otherwise the NPC emotes their boredom of the PC's small talk.

 

I like how this works in play. I created a function for getting small talk so that I can later change the method. Currently I am using local strings set on the NPC, but it would be very cool to move conversation snippets to a MySQL database and then create a web interface for generating and categorizing "small talk". I think some of these could be rumors too even though small talk should mostly be flavor text, and I should add a rumors list or "crunchy" information bit for [Ask for information]



string DlgGetSmallTalk(object oPC, int nSmallTalkCount)
{
    string sSmallTalk   = GetLocalString(   OBJECT_SELF,
                                            "DIALOG_SMALLTALK_"
                                            +IntToString(Random(GetLocalInt(OBJECT_SELF,"DIALOG_SMALLTALK_COUNT"))+1)
                                        );
    if(sSmallTalk=="")
    {
        switch(d4())
        {
            case 1: sSmallTalk="*Chats about the weather.*"; break;
            case 2: sSmallTalk="*Jokes with you.*"; break;
            case 3: sSmallTalk="*Gossips with you.*"; break;
            case 4: sSmallTalk="*Shares an anecdote.*"; break;
            default:break;
        }
    }

    return sSmallTalk;
}


               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Generic NPC Conversation Scripting
« Reply #29 on: April 27, 2014, 11:00:29 pm »


               

I've been making slow progress on Inn/House Keepers, and thus had to revisit my Persistent Inn System which still needs a fair amount of work as I had neglected to complete a number of key functions.


 


Since this is getting fairly complicated I abstracted out the conversation mode checking/establishing function. This shows the main branches of the conversation at this point, although each of those categories allows drilling down into them as well.



void DlgSetStartingPage(int bConvContinue, int bUnequippingWeapon=FALSE)
{
    object oPC          = GetPcDlgSpeaker();
    int nNPCInterest    = DlgGetNPCInterestInConversation(oPC);
    int nConvLang       = GetCurrentLanguageSpoken(oPC);

    if(     !bUnequippingWeapon
        &&  !GetLocalInt(oPC, "CONV_WEAPON_WIELDED") // a PC can continue a conversation with the weapon wielded
        &&  GetIsWieldingWeapon(oPC)
      )
    {
        SetDlgPageString(PAGE_CONV_WEAPON); // this is a temporary awkward moment when conversation starts
        SetLocalInt(oPC, "CONV_WEAPON_WIELDED", TRUE);
    }
    else if(nNPCInterest<0)
    {
        SetDlgPageString(PAGE_CONV_REJECT);
    }
    else if( !GetSpeaksLanguage(OBJECT_SELF, nConvLang) )
    {
        SetDlgPageString(PAGE_CONV_LANG);
    }
    else if(GetLocalInt(OBJECT_SELF,"HOUSE_KEEPER"))
    {
        SetDlgPageString(PAGE_CONV_HOUSE);
        SetLocalInt(OBJECT_SELF, "CONV_LANGUAGE_SPEAKING", nConvLang);
    }
    else if(GetLocalInt(OBJECT_SELF,"MERCH"))
    {
        SetDlgPageString(PAGE_CONV_MERCH_GREET);
        SetLocalInt(OBJECT_SELF, "CONV_LANGUAGE_SPEAKING", nConvLang);
    }
    else if(bConvContinue||!nNPCInterest)
    {
        SetDlgPageString(PAGE_CONV_CORE);
    }
    else
    {
        SetDlgPageString(PAGE_CONV_GREET);
    }
}