Here are some observations I used to guide me in creating an unusual system of NPC conversation in my interactive fiction story, Bear Creek.
- NPCs can initiate conversation
- NPCs can talk to each other
- NPCs can launch into multi-turn speeches/rants
- Conversations can be interrupted and resumed
- NPCs can talk about unrelated things
- NPCs have things they want to talk about
- NPCs conversations can be triggered by keywords
- Conversations rarely have a definitive end
For each of these observations, I structured elements of a system for NPC conversation in Bear Creek.
NPCs can initiate conversation
This is simple and increasingly common in modern IF, non-player characters that are proactive in addressing the player. With Inform 7’s use of “scenes,” this can be implemented simply and elegantly.
There is a scene called Grandparents Conversation. Grandparents Conversation begins when player is in Blackberry Clearing for the first time. When Grandparents Conversation begins: try saying hello to Grandpa; now current interlocutor is Grandpa;
NPCs can talk to each other
Often there is a tension in Interactive Fiction authoring between modeling and storytelling. I understood that I could have made a system whereby each NPC took an action or made an utterance, resulting in another NPC responding with their own action or a spoken response, and so on. I considered this.
I also considered the amount of effort that would have taken and what I would have bought with my troubles.
I also knew that when NPCs perform an action and the game engine dutifully reports on that action, the results may be less than impressive. You might want something like “Grandpa catches up to you, breathing heavy,” and get something more like “Grandpa enters the room.” And dialogue full of nuance and gesture would be even more difficult.
Some times it’s okay to fake it, especially if the results are considerably better. So I often reported the doings and sayings of NPCs without modeling that behaviour:
say "'That sheriff came by again asking about Lee,' Grandpa says. [paragraph break]'I'm not sure why you trust him,' Honey says. 'I'd be happy if I never saw that guy again.' [paragraph break]'The sheriff or Lee?' Grandpa asks. [paragraph break]'Both!' Honey laughs.";
This is actually part of a 13 to 20 step sequence of overheard dialogue between two characters.
NPCs can launch into multi-turn speeches/rants
This was important to me, the idea that certain subjects brought up with particular NPCs start them off on a little rant. A series of utterances that happen over several turns, regardless of what you may say to them or ask them.
It might look like this:
>ask grandpa about mom
“Your mom? What’s on your mind, Bud?” Grandpa looks serious, “Don’t worry about her. She can take care of herself. Your mama will always love you more than anything in the whole world.”
>tell grandpa about train
Grandpa didn’t seem to hear you.
“Your mom’s just gone to work things out with your step-dad.” Grandpa forces a smile, “You watch, she’ll be back in a jiffy. She should be home tomorrow. But she’ll be okay.”
>ask about mark
Grandpa still has more to say.
“Your mama will be just fine,” Grandpa’s says.
To do this, I created a type of thing called a rant with an in-progress property; an every turn rule that dealt with stepping through the rant; and an instead rule to intercept the player talking to the speaker of a rant.
Section - Multi-Part Rants A rant is a kind of thing. A rant has a table name called the quote_table. A rant has a person called the speaker. A rant has a room called the orig_room. A rant has a number called the index. A rant can be in-progress. It is usually not in-progress. A rant can be run. It is usually not run. Every turn: repeat with this_rant running through every in-progress rant: step a rant for this_rant; To step a rant for (this_rant - a rant): if index of this_rant is zero: now this_rant is run; now orig_room of this_rant is the location of the player; increase index of this_rant by one; let index be index of this_rant; let orig_room be orig_room of this_rant; let max_quotes be the number of rows in quote_table of this_rant; if player is in orig_room and speaker of this_rant is in orig_room: queue report quote in row index of the quote_table of this_rant with priority 1; if index is max_quotes: now this_rant is not in-progress; [ we repeat the last quote from here on out ] now index of this_rant is max_quotes minus one; [say "rant [this_rant] - in progress (index: [index of this_rant]; rows: [number of rows in quote_table of this_rant], number: [number of in-progress rants]).";] Instead of doing something to someone (called the target): if we are speaking to the target: repeat with this_rant running through every in-progress rant: if the speaker of this_rant is the target: say "[The target] [one of]didn't seem to hear you[or]is still talking[or]still has more to say[or]is still going on[at random]." instead; [stop the action;] continue the action;
An individual rant is simply defined. Here’s the one modeled above (also using Eric Eve’s Conversational Response as the trigger):
Response of Grandpa when asked-or-told about Mom: now gpa_mom_rant is in-progress. gpa_mom_rant is a rant. The quote_table is the Table of gpa_mom_rant. The speaker is Grandpa. Table - gpa_mom_rant Quote "'Your mom? What's on your mind, [grandpa's nickname]?' Grandpa looks serious, 'Don't worry about her. She can take care of herself. Your mama will always love you more than anything in the whole world.'" "'Your mom's just gone to work things out with your step-dad.' Grandpa forces a smile, 'You watch, she'll be back in a jiffy. She should be home tomorrow. But she'll be okay.'" "'Your mama will be just fine,' Grandpa's says."
queue report "something" with priority x
is just a routine that attempts to output stuff going on within the turn (dialogue, ambient happenings, NPC actions, etc) in the most dramatically satisfying order.
Conversations and action sequences can be interrupted and resumed
While the rants defined above don’t stop and start when you leave the room, more elaborate action sequence can. While there are NPC conversations that continue whether you are there or not, some sequences might only be meaningful if the player is there.
Therefore I wanted to create sequences with arbitrary actions and affects within the game coupled with arbitrary tests to see if the sequence should pause.
Similar to the rants, I created a thing with an in-progress property; and an every turn rule to step through any in-progress sequences. Each defined sequence is a bit more complicated than a rant, having to include an interrupt test.
Chapter - Sequences A sequence is a kind of thing. A sequences has a rule called the action_handler. A sequences has a rule called the interrupt_test. A sequences has a number called the index. A sequences has a number called the number_of_actions. A sequences has a number called the turns_so_far. A sequence can be in-progress. It is usually not in-progress. A sequence can be run. It is usually not run. Every turn: repeat with this_seq running through every in-progress sequence: step a sequence for this_seq; To step a sequence for (this_seq - a sequence): now this_seq is run; increase turns_so_far of this_seq by one; follow the interrupt_test of this_seq; if rule failed and number of in-progress rants is zero: increase index of this_seq by one; follow the action_handler of this_seq; if rule failed: decrease index of this_seq by one; if index of this_seq is the number_of_actions of this_seq: now this_seq is not in-progress; now index of this_seq is zero;
Here is a relatively simple sequence:
Section - Cat Lady Invite Sequence catlady_invite is a sequence. The action_handler is the catlady_invite_handler rule. The interrupt_test is catlady_invite_interrupt_test rule. The number_of_actions is 2. This is the catlady_invite_handler rule: let index be index of catlady_invite; if CatLady is visible: now current interlocutor is CatLady; if index is 1: if player is in C Loop and CatLady is visible: queue report "[one of]'Oh, hello, [Cat Lady's Nickname],' the Cat Lady says, 'So good to see you. Out for an adventure today?'[or]'Oh, hi again, [Cat Lady's Nickname]. Will you spend a few minutes talking to your old neighbor?' the Cat Lady says.[stopping]" at priority 2; else if index is 2: if player is in C Loop and CatLady is visible: queue report "'Won't you come in for a moment?' the Cat Lady gestures at her trailer, 'I just love guests. And I do so enjoy talking to you.'" at priority 2; This is the catlady_invite_interrupt_test rule: if we are speaking to catlady, rule succeeds; if Sheriff's Drive-By is happening, rule succeeds; rule fails.
This sequence is triggered by a scene:
When A Visit With the Cat Lady begins: if Cat Lady Tea Time has not happened: now catlady_invite is in-progress.
NPCs can talk about unrelated things
The rants satisfied this conversational property for me. This coupled with the occasional oblique response to a direct question.
NPCs have things they want to talk about
Using scenes plus sequences allowed NPCs to carry on elaborate conversations, pursue questioning, and grill the player character.
In Bear Creek, I am blessed with particularly idiosyncratic characters for whom a nonsequitar or oddball response is nothing out of the ordinary.
To say cat lady prattle: say "[one of]The Cat Lady leans toward you. 'Tell me about your [one of]explorations[or]adventures[or]wanderings[at random]. What about [one of]the train tracks[or]the creek[or]blackberries[or]the swimming hole[or]the big pine tree[at random]?'[run paragraph on][or]What is the news with your [one of]grandpa[or]Aunt Mary[or]mom[at random]?' the Cat Lady asks.[run paragraph on][or]The Cat Lady gestures at your cup, 'Do you like your tea?' she asks[one of][or] again[stopping].[run paragraph on][or]The Cat Lady looks serious. 'Are things going okay with your new step-dad? Is that going okay?'[run paragraph on][or]'Is your grandmother still mad at me?' the Cat Lady asks.'She's been angry at me for years. Ever since I spoke up about her and Joseph's friendship.' The Cat Lady is lost in thought.[run paragraph on][in random order]".
NPCs conversations can be triggered by keywords
This is something I haven’t implemented in Bear Creek. This mirrors the human tendency to grasp on to the words we heard, even if we don’t always understand the rest of what was said.
I thought of this as something like a domain-specific Eliza. A last ditch effort to squeeze a conversational save out of an otherwise misunderstood player input. In other words, after the parser has given up on the player’s input a routine looks for keywords in the input: “blah blah blah keyword blah.”
> ask if grandpa has ever climbed the big pine tree
“The big tree up by the road? That’s called a Doug Fir,” Grandpa says. “They’re tall and straight, but sometimes blow down if there’s a big storm.”
Not exactly on-point, but better than “You can’t see any such thing.”
Conversations rarely have a definitive end
This is largely satisfied by the rants and sequences. Combined with Eric Eve’s Default Responses, Bear Creek provided some satisfying results:
>ask honey about step-dad
“Now, here’s a guy who has some problems, but really knows how to generously share them with others,” Honey says. Grandpa gives her a Look.
>go to brambles
“Oh, don’t let me keep you,” says Honey sarcastically as she goes back to her berry picking.
Overall, precision is not needed in this context. This is conversation, an inexact exercise at the best of times, and often an amusing comedy of errors full of misunderstanding, misdirection, and misinterpretation.
Hopefully with these observations and stabs at implementation, we take a step away from the illusion-shattering precision of the computer and a step toward the magical imprecision of human conversation.