Skip to content

Library: Conversations

ThePix edited this page Jun 15, 2018 · 3 revisions

NOTE: This is not applicable to the on-line editor, which does not support custom libraries.

There are several approaches to conversations in interactive fiction, and Quest can handle most without a problem.

This page is about one specific approach, using dynamic menus. I wrote a library for this some years ago, and it has proved to be one of the most popular. You can download the latest version here (see here for how to add a library to your game):

ConvLib

An Example

Let us suppose we have a character called Mary, and we want the player to talk to her about a recent murder, and as the conversation progresses, the choice of topics will change. If the player accuses Mary of the murder, Mary will refuse to say any more, but if asked about what she was doing, she might reveal some vital clue.

Getting Started

Once you have included the library in your game, the first thing we need is someone to talk to, so create an object, call it Mary, and set it to be a "Female character (named)". Then go to the Conversation tab, and set her to be "Character". We will ignore everything else on that tab for now.

We will start by giving Mary two topics. The player can ask what she was doing the previous evening, and how well she knew Dr Black. Each topic is an object, and needs to be inside Mary. Right click Mary, and select "Add object". Give the object a suitable name (I like to start with the character name, so perhaps "mary alibi"), then select Mary from the dropdown box, and click Okay. You can also drag the topic on to Mary if it is in the wrong place.

On the Setup tab, type in the Alias box type in what the player will see as a menu choice, perhaps: "Where were you last night?". Note that all topics must have an alias; you will get an error if there is no alias.

Now go to the Conversation tab, set it to be a "Starting Conversation Topic", and type stuff in the conversation box. Let us say, for example, that Mary says that she was playing pool with Clive.

Do the same to create a topic allowing the player to ask how well she knows the doctor.

Then go into the game, and see what happens. You should find that when you talk to Mary you are presented with a menu, and can make a selection. If you talk a second time, the one you have already used has gone from the menu.

Follow-on Topics

Often a subject will open up new topics. For example, if Mary's alibi was that she was playing pool with Clive, the player may want to ask more about that.

Create a new topic as you did before, say "mary playing pool", but this time set it to be a "Conversation Topic" instead of a "Starting Conversation Topic". Put in some suitable dialogue.

Now go back to the "mary alibi" topic, and in the section above the conversation, click the Add button. Now type in the name of the topic you just created (you need to get this exactly right).

Go in game, and you can see a dynamic conversation, with topics getting added to and removed from the list.

The player might want to find Clive and ask him about the alibi too. It works just the same. You will need to create a new character, Clive, and give him a topic, let us say "clive playing pool". Now go back to Mary, and add a new follow-on topic there, called "clive playing pool"; it should appear in the list under "mary playing pool". When the player sees the alibi topic, both these new topics will become available, one for Mary and one for Clive.

Exclusive topics

Conversations become more of a challenge to the player if one topic prevents another. For example, you might have a topic "Accuse Mary of murder", and another "Ask Mary for a date". You could set up the "Accuse Mary of murder" to hide the other topic.

Even better, you could have two objects with the same alias; "Ask Mary for a date", and give one a friendly response and one angry. The "Accuse Mary of murder" topic could then hide the friendly version, and show the angry one.

Introductions

Before you start interrogating Mary, perhaps you should introduce yourself? Go to Mary's Conversation tab, and type an introduction in the greeting box. This will appear before the rest of the text the first time the player talks to her.

Room Specific Topics

You can create a conversation topic in a room. If it starts with the character's name, then it will get added to menu of topic choices. The usual rules about starting topics apply.

Topics are flagged as not visible, so will not appear in room descriptions.

This does mean that in theory you could put all your topics in rooms rather than in the appropriate character, and that would work, as long as they never move around. Personally, I think it is better to keep them connected to the character as much as possible.

Adding more topics

Often you will want new topics for a character to became available as a result of the player seeing or doing something. Use the ShowTopic function to do this.

The corresponding HideTopic function can be used to hide a topic that is no longer available.

Repeating topics

By default, topics are hidden after they are seen, but you can stop that for a specific topic by unticking the "Automatically hide" checkbox.

Sometimes you might want a topic to keep appearing in the list. Perhaps the character has asked for some item, and the player can use this topic to give it to her. The player can keep picking the topic, but it only disappears once the item is handed over.

Animals and gags

If either the player or the character cannot speak for some reason, you can add a string to the relevant one called "gagstring". If the player attempts to talk, this message will be displayed instead.

  player.gagstring = "You can't talk now; your mouth is full of gob-stoppers!"

Set the attribute to null to allow conversation again.

  player.gagstring = null

In previous versions you could flag a character as an animal. The current version has a function called PlayerCannotTalkTo, which can be used to stop the player talking in some situations, and we will set it up to do the same. The function takes a single parameter, an object called "c", and returns a Boolean.

Click here for how to over-ride a function.

Paste in this code:

if (GetBoolean(c, "animal") and not GetBoolean(game.pov, "talktoanimals")) {
  msg ("You spend a few minutes talking to " + GetDisplayName(c) + ", but something seems to be missing from the conversation... like any indication the creature understands a word you say.")
  return (true)
}
return (false)

If you are writing your own custom function, it will need to test for any situation where the player cannot speak, and it that is the case, it must give a message saying why, and then return true. Otherwise, it should just return false.

To get the code above to work, you should flag every animal by giving it an "animal" attribute set to true. If the player has some special effect that allows her to talk to animals, set "talktoanimals" on the player to true.

Active Conversations

There is an "active conversation" option for characters. If selected, the conversation automatically continues, if there are still options available (rather than the player typing TALK TO... again). There is also a "force conversation" option; if selected the player will have to select a conversation option before moving on (by default the player can choose to ignore the list of options and do something else). Note that if both options are selected, and you have topics that do not disappear after they are used, the player could get stuck in a loop, so be careful!

Scripts

Your conversations can run scripts too. When a topic is select, the text is displayed first, and the script is then run, if there is one. This is useful when you want the topic to trigger something, perhaps to have a character start (or stop) following the player.

Consultables

A new feature is the "consultable" type. Use this for encyclopedias and computers that the player can use to look stuff up on.

Topics are set up exactly as with conversations. In this case you probably want to untick the "Automatically hide" checkbox for each one.

At its simplest, that is all there is to it. If you set up the computer as a "switchable" object, the player will only be able to use it when it is turned on. You might want to change the "consult" script to fit your system, for example to check if the computer is connected to the internet.

Go to the object's Attributes tab, and find consult (it will be in grey). Click on Make editable copy, and then you can modify it as you wish.

This example just changes the message if it is not turned on..

  if (not this.switchedon) {
    msg("You wonder if you should turn on the computer to use it.")
  }
  else {
    Converse(this, "Consult ")
  }

Display and Hidden

Behind the scenes, each topic has two flags controlling whether it will appear in the menu list or not, display and hidden. The topic will only get show if display is true and hidden is false. Any other combination and it will not appear.

What this means is that if you set a topic to be hidden, and later set it to be displayed, the hidden will take priority, and it will stay hidden.

To get a topic to display, use the ShowTopic function. To hide it, use HideTopic.

  ShowTopic(topic)

  HideTopic(topic) 

Hiding topics is useful when a player's actions stops that being an option. For example, if the player attacks a character, you might want to change what topics are then available.

Tracking status

Setting something to be a Character will automatically give it an integer attribute called state, set to zero. This is not used by the conversation system, but can be useful for you when you are tracking the state of a character.

Every topic has an integer attribute called count, set to zero. This will be increased by one each time the topic is seen, so gives a quick way to check if the player has seen it yet.

Testing

There is a function called TopicTest that will do some basic testing. You can add it to game.start to check your game; it will flag any obvious issues.

  • Topics without aliases

  • Topics that try to display new topics that do not exist

Delete it from game.start before publishing!

Alternative Topic Placement

Rather than having the topics inside the character, you can create a new room, and put them all in there. The new room must be named exactly the same as the character, but with "_topics" appended. You may find this more convenient, especially if the character is carrying several items anyway.

Note that you cannot have some topics in the character and some in the topics object. However, you can use one for one character and the other for another.

Going a step further, you could override the GetTopics function, giving more control over where topics are. Let us suppose you have a game where the player can swao between any of several characters in the game, and you want each non-player character to have its own list of topics for each player character. One way would be to have a room for each combination. In this example code, if there was a non-player character called Mary, and a character the player can be called Boris, it will look in a room called "Mary_topics_for_Boris" when Boris talks to her.

lst = NewObjectList ()
topics = GetObject(char.name + "_topics_for_" + game.pov.name)
foreach (obj, GetDirectChildren(topics)) {
  _AddTopic (lst, obj)
}

// now grab topics for the room
foreach (obj, GetDirectChildren(game.pov.parent)) {
  if (StartsWith(obj.name, char.name + "_" + game.pov.name)) {
	_AddTopic (lst, obj)
  }
}
return (lst)

Topics That Expire

There is a time and place for everything, and what may be a witty retort one moment is a sad and bitter comment ten minutes later. To have topics expire, tick the checkbox, and then put the number of turns it will be until it disappears.

More customisation

Various functions and templates can be overriden (i.e., you create your own version of them in your game), to further customise the library.

These are the dynamic templates it uses:

	<dynamictemplate name="NoSpeakTopics">"You wonder what you can talk to " + GetDisplayName(object) + " about..."</dynamictemplate>
	<dynamictemplate name="NoConsultTopics">"You wonder what you can look up with " + GetDisplayName(object) + "."</dynamictemplate>

AddExtraTopics You can use this as a way to add extra topics, for example if you want all your characters to have some standard topics, add them this way.

  <function name="AddExtraTopics" parameters="c, topics">
  </function>

AfterTopicEvent Override this to have something happen after each exchange.

  <function name="AfterTopicEvent">
  </function>

Phone a friend

See here for how to use Convlib to add a smart phone to your game.

Reactions

The Conversation Library offers an advanced feature that will help add life to your characters.

The reactions facility is designed to be a way to have NPCs react to what the player has done. For example, if the player suddenly appears wearing a Mickey Mouse costume, it would be reasonable to expect an NPC to comment on it. This is a system that makes having an NPC comment on what the player has done fairly easy - and by easy, I mean less work, not necessarily conceptually simple.

The basic idea is that each NPC has a set of reactions, and the system tests them to see if any one reaction is applicable.

It is assumed that most of these reactions are just text printed to screen. If you want complex effects, you may need something beyond this (but it should be possible to do that in addition to this system).

Assuming you are already using ConvLib, the first step is to turn the system on. Go to the game object, and add a new Boolean attribute, showreactions, and set it to true. Now if you go to the Conversation tab for an NPC, there will be a new section, "Reaction script".

Reacting to the Player Wearing a Hat

As an example, let us suppose there is an NPC called Mary, and that the player can pick up and wear a couple of hats, one plain and one fancy. If Mary sees the player in a hat, we want her to comment on it. Also, if she sees him in the plain hat first, but later in the fancy hat, we want her to react to that too.

Let us start by noting that this is unlike anything else in Quest! We will be typing instructions in the box, but not as a script. Each reaction is composed of four parts, the first three each getting their own line, while the fourth part can be as many lines as you like.

  1. The first part is just a @ on its own. This indicates the start of a reaction.

  2. The second part is a name for the reaction; whatever you like that identifies this reaction. It need not be unique, and in fact having several with the same name can be useful, as described later.

  3. The third part is Quest code; a condition or set of conditions. When it evaluates to true, this reaction will be done. If it evaluates to false, it will not. You can use and and or as normal to link conditions together. You can also use this as normal to refer to the NPC.

  4. The fourth part is what will happen. This can be any number of lines, and each will be processed separately. Most will be treated as text, and will go straight to the screen. You can use text processor commands as normal.

With that in mind, here is the reaction text for Mary:

@
plain hat
plain_hat.worn
'Oh, you are wearing a hat,' says Mary.

@
fancy hat
fancy_hat.worn
'Wow, what a fancy hat,' says Mary.
~plain hat

As you can see, there are two reactions, each starting with a @. The next line is the reaction name, and then there is condition. In this case, we are testing whether the worn flag is set to true. If it is, that reaction will be done. Once a reaction has been done, it is removed from the list, so it can only ever happen once.

After the condition, there are the events. The first just has some text to print. The second has that two, but the last line start with a ~, followed by the name of the first reaction. The ~ indicates that the named reaction should be deleted (in addition to this one).

Mutually Exclusive Reactions

Let us now suppose the player can win a trophy, and we want Mary to react to that. We want two reactions, one for Mary encountering the player with the trophy and one for when he does not have the trophy. However, we only ever want one to fire.

@
trophy
player.won_competition and trophy.parent = player
'Cool trophy,' says Mary.

@
trophy
player.won_competition
'Hey I heard you won the completion,' says Mary.

What we could have done is use the ~ to tell the system to delete the other reactions, but in this case that is not necessary. The system deletes _all_the reactions with the same name, so when either one of these gets used, the system deletes all the reactions called "trophy" (for this NPC).

Modifying Attributes

The system offers limited support for setting attributes. Here is an example to illustrate the basic format:

@
trophy
player.won_competition
'Hey I heard you won the completion,' says Mary.
= this alias "Happy Mary"
+ Mary happiness 5

Each instruction gets a line on its own, and the line has to start with =, + or -. Then there is the name of the object, followed by the attribute name, and then the value, all separated by spaces. Note that neither the object nor the attribute can contain spaces, however, you can use "this" to refer to the current NPC.

The + and - can be used with integer attributes only, and will add to or subtract from the current value. If the attribute does not exist, it will be created and given a default value of zero before the addition or subtraction.

Running Scripts

A final otion is to run a script. This is done in a similar manner to setting attributes, but uses a & at the start of the line. In this example, the "greeting" script of Mary will be run (as before, I could have used "this" to refer to the current NPC).

@
plain hat
plain_hat.worn
'Oh, you are wearing a hat,' says Mary.
& Mary greeting

Notes

The system does not use "this" in the normal way; it cheats and substitutes "this" with the name of the character. Generally you will not notice the different, but if you have a name or something in the condition that includes the string "this", perhaps as part of a longer word or name, this will screw up.

Clone this wiki locally