This is part 2 of a series.  If you haven’t read the first part, you might want to start there.  In that post, I describe how to use Aaron Reed’s Keyword Interface extension alongside Eric Eve’s Conversation Package family of extensions to implement highlighted topic lists for Inform 7 conversations.

In this post, I’ll share how to implement single-keyword conversation analogous to what Aaron Reed does in Blue Lacuna, and also share a few bugfixes for the code from the first section.  By the end of these two posts, you should have the tools you need to integrate these two extensions and implement a robust, TADS 3-like conversation engine using highlighted topic keywords and a single-keyword user interface, with surprisingly little work.  Of course, coming up with good content is another matter…

The documentation, unfortunately, isn’t going to be as helpful this time around; we’ll be grunging through source code quite a bit.  A logical place to start is with Keyword Interface itself; we can look at the code for how Aaron handles single keyword examining and see if that is a good starting point.

And it is!  Here’s how Aaron does it:

Understand "[a thing]" as examining.

Utterly simple. Making this work for topics also, however, isn’t going to be quite as simple, since conversation is not appropriate in all circumstances as examining is, and since we’ll have to coexist with Aaron’s examining code — many of the topics we’ll be talking about are examinable things as well, and we need to properly distinguish between examining them and talking about them. In particular, we only want to talk about a keyword if we are engaged in a conversation. In Eric’s extensions, this is denoted by “current interlocutor” being set to someone other than the player.

So what makes something a keywordable topic?  We’re using Eric Eve’s Conversation Package, so really what we’d like is a superset of the things that show up in the suggested topic list.  That is to say, everything in that list should work, if possible, but we shouldn’t be limited to just the things in that list.  Conversation Package lets us easily handle the player asking or telling about things that aren’t in that list, so we should preserve the ability to tag arbitrary topics if we choose to as well.

To that end, let’s establish some definitions to make our lives easier.  Under Conversation Suggestions and Conversation Nodes, every person and convnode object has three list properties:  add-suggestions, tell-suggestions, and other-suggestions.  These three lists, put together, are used to create the suggested topic lists.  So if we can make a classification that includes items in those three lists, we’ve covered the topics portion.  In order to support topics that fall outside this categorization, I also added a fourth list property to every person and convnode:  “unsuggested-topics”.  This will be used to hold topics for which we want keyword support but which we don’t want included in the standard topic suggestion list for some reason.

A person has a list of objects called unsuggested-topics.
A convnode has a list of objects called unsuggested-topics.
 
Definition: a thing is ask-topical if it is listed in the ask-suggestions of the current interlocutor or it is listed in the ask-suggestions of the current node.
Definition: a thing is tell-topical if it is listed in tell-suggestions of the current interlocutor or it is listed in the tell-suggestions of the current node.
Definition: a thing is other-topical if it is listed in other-suggestions of the current interlocutor or it is listed in the other-suggestions of the current node.
Definition: a thing is unsuggested-topical if it is listed in unsuggested-topics of the current interlocutor or it is listed in unsuggested-topics of the current node.
 
Definition: a thing is topical if it is ask-topical or it is tell-topical or it is other-topical or it is unsuggested-topical.
Definition: a thing is non-topical if it is not topical.

Now that we have that established, we can add our “understand” code.  First, we add our topical actions:

Understand "[any ask-topical thing]" as implicit-quizzing.
Understand "[any tell-topical thing]" as implicit-informing.
Understand "[any other-topical thing]" as implicit-quizzing.
Understand "[any unsuggested-topical thing]" as implicit-quizzing.

The implicit-XXX actions only apply in the context of a conversation, when we have a current interlocutor. This is fine, though, since the way we’ve set things up we won’t be seeing topic keywords unless we’re already in conversation. When you consider that Eric Eve set up “a [any known thing]” and “t [any known thing]” as the commands for implicit-quizzing and implicit-informing, this doesn’t seem like a huge syntactic leap, does it?

We’re going to assume that if they are using keywords for “other” suggestions or unsuggested topics, that we can safely direct them to the “ask” handler, but of course you’ll have to structure your code appropriately to make that work, and you can do something different if it’s more appropriate.  Note the “any” in the statements above as well.  This lets us match anything, not just things in our current scope. This is in case we want to ask someone about a coconut they left three rooms away.

It’s also worth noting that we are using only the thing-centric actions for our keyword-based conversation here. The reason for this is that although you could write something like:

Understand "[text]" as implicit-asking when the current interlocutor is a non-player person.

…and it would work correctly in context, it would also have the unfortunate effect that any mistake in a command you typed that the parser interpreted as text would be handled as a conversational command when you were involved in a conversation. It may be that you are OK with this behavior, and in that case you can add the above line. I’ve chosen not to, and to consciously limit myself to the quizzing and informing actions. This doesn’t seem like an arduous constraint in practice, as you can easily create things to correspond to any text-only topics you want to use. You’ll type a bit more, but I think the game behaves in a cleaner way as a result.

Next, we have to short out part of Keyword Interface’s examine code:

Section 2 Topic Keyword Overrides (in place of Section - Examine Grammar Line in Keyword Interface by Aaron Reed)
 
Understand "[a non-topical thing]" as examining.

Why did we have to do this?  Because otherwise the examination logic would override the topic logic, and you’d be trying to examine things in conversation instead of talking about them.  With this change, we can ensure that a keyworded thing behaves correctly when the user types the keyword:  it will examine when you’re not in conversation, and ask about it when you are talking to someone.

So is that it?  Almost.  What we have now will work, but there are problems when trying to use the obvious keyword for the “self-suggestion” entry provided by Conversation Suggestions to handle asking about the current interlocutor.  That suggestion object prints out as “himself” or “herself” as needed, but if you type that in as a single keyword the system won’t understand it.  And that makes sense, because that object isn’t properly set up to be described by “himself” or “herself”.

What you can do to deal with this issue is to create a specific self-suggestion for each interlocutor like so:

Captain-suggestion is a familiar man.
The printed name of captain-suggestion is "himself".
The captain-suggestion has an indexed text called identifier.
The identifier of captain-suggestion is "himself".
Understand the identifier property as describing captain-suggestion.

Now you can include the object in one of the suggestion lists. It will print out correctly, and respond to the appropriate keyword. What you’ll need to do is set up a redirecting handler so that asking about “captain-suggestion” gets forwarded to asking about the actual captain. Why? Because we can always just type in “ask captain about captain”, without keywords, which will look for a response targetted to “captain”, not “captain-suggestion”. If you don’t do this, you’ll either miss a description for one or the other, or risk getting the descriptions out of sync.

The last issue is minor, but significant. Eric Eve forces “himself” or “herself” first in the list of topics, if it exists, regardless of where in the list you put it. When we bypass his self-suggestion object, we lose this behavior. We can get it back, however, if we slightly change the Complex Listing rule from the last post:

To set up (l - a list of objects) for topic printing:
	repeat with item running through l:
		now the item is marked for special listing;
	register things marked for listing;
	repeat through the Table of Scored Listing:
		if the printed name of the output entry is "himself" or the printed name of the output entry is "herself" or the printed name of the output entry is "itself":
			change the assigned score entry to 0;
		otherwise:
			change the assigned score entry to 1;

And that finishes it up! You now have keyworded topics working side-by-side with Aaron Reed’s Keyword Interface, handled by Eric Eve’s Conversation Package!

I hope these articles have been helpful to you — I think what we’ve ended up with combines the best features of two excellent extension packages, and should help give you the tools to help you easily write powerful conversations with a very accessible and attractive UI.

As a side bonus, here are some code changes I made since the last post that make the keyword highlighting even easier.

First, I originally had handled keyword printing with a rule like so:

Rule for printing the name of something (called item) when listing suggested topics:
	say "[t][printed name of the item][x]";

That works, but it doesn’t automatically highlight only the final word of the item like Aaron’s other keyword code does. You can write rules for specific items to do this, but it’s even easier to lift his code and tweak it for the topic case:

Rule for printing the name of something (called item) when listing suggested topics:
	let output be indexed text;
	now output is the printed name of item;
	let kw be indexed text;
	now kw is the keyword of item;
	if kw is "":
		change kw to word number ( the number of words in output ) in output;
	repeat with wordcounter running from 1 to the number of words in output:
		say "[if wordcounter > 1] [end if]";
		if word number wordcounter in output matches the regular expression "\b(?i)[kw]":
			say "[t][word number wordcounter in output][x]";
		else:
			say "[word number wordcounter in output]".

Now you get equivalent support to his examining keywords — a keyworded topic will highlight the last word by default, and can be overridden by setting the keyword property.

I also found a slight bug where you would get inappropriate topic highlighting if something triggered the “nothing specific to say” topic listing response. If you choose not to use topic suggestions but forget to remove the “topics” command, you’d get a message that would highlight the current interlocutor as a keyword if he or she was configured for examination keywording. Nothing breaks, but it looks bad. A slight modification as follows fixes that problem:

To say nothing specific:
	say "You have nothing specific in mind to discuss with [the printed name of the current interlocutor] right now.";

This directly prints the name, without formatting.