Facts

Overview

Using <.reveal> and <.inform> tags helps keep track of what the player character and other NPCs know, but in a game of any size in which substantial amounts of information are exchanged it can be tricky to keep track of just what has been communicated to whom. For example, if in an exchange with Bob the tag <.reveal troubles> is used in Bob's response to a question about the lighthouse, and then an <.inform troubles> tag is used in part of what the player character tells Janet, just what has Bob told the player character about the troubles, and is it the same as what the player character goes on to tell Janet? As our game grows larger and we make increasing use of such tags, it can become increasingly hard to ensure we're using them consistently not only in our <.reveal> and <.inform> tags but also our isActive = gRevealed('tag') conditions on TopicEntries. The Facts module offers a framework that can help impose order on the potential confusion and help automate a number of knowledge-related tasks. Its functionality can be extended, if needed, by also including the Fact Relations extension.

The Facts module extends the adv3Lite knowledge system by allowing game authors to define Fact objects that encapsulate (and help to track and use) the information associated with <.reveal> and <.inform> tags, so for example there can be Fact object called 'rain-tomorrow' that encapsulates what information has been revealed by a <.reveal rain-tomorrow> tag and who knows about it and what game objects or topics it concerns. Note that in this context a Fact is something that has been asserted to be true by some person or object (e.g. book) in the game; not something that is necessarily in fact true.

Note that if the facts.t module is present in your game, your game must also include actor.t, topicEntry.t and thoughts.t.

The Facts module defines the following new classes and objects:

In addition, this module adds a number of fact-related methods to the TopicEntry and ActorTopicEntry classes to facilitate the uses of Facts in conversation.

These various classes enable the contents of Facts to be listed in response to commands like THINK ABOUT and LOOK UP under suitable conditions as well as keeping track of who has told the player character what, provided they are all used together in the correct way. This will all be explained in more detail below.

The Fact Class

A Fact encapsulates something that has been asserted as true (even if it may not be so); it does not necessarily correspond to what anyone believes to be the case. Indeed, a game might contain Facts that make contradictory assertions. See the section on Knowledge, Truth and Belief for how to associate a character's evaluation of a fact with the fact in question.

The Fact class provides the following properties and methods for use by game authors:

The most commonly defined properties of any Fact are its name, topics, desc, and initiallyKnownBy properties. The library accordingly defines the following Template to facilitate Fact object definitions:

Fact template 'name' [topics]? 'desc' [initiallyKnownBy]?;

So a simple Fact might be defined like the following example:

Fact 'madrid-capital' [tSpain, tMadrid]
   desc = 'the capital of Spain is Madrid'
   [me, travelBook]
;

Note that we don't need to give a Fact a programmatic name; it's known by its name property, and can be retrieved by calling factManager.getFact(name) or just gFact(name). This example assumes that the player character object is called me, and that travelBook is a Consultable defined elsewhere in the game along with the Topics tSpain and tMadrid. We use me rather than gPlayerChar here, since if the player character changed during the course of the game, it would still be me to whom this Fact was initially known rather than the new player character, and this makes it clearer.

The factManager Object

The purpose of the factManager object is to store a reference to every Fact defined in the game at preinit, and then provide a number of methods to allow Facts to be retrieved by their (single-quoted string) name. The most important factManager methods for game authors are:

This should all become clearer with some examples of use when we put everything together below.

Note that factManager.getFact(name) can be abbreviated to the macro gFact(name) and factManager.getFactDesc(name) to the macro gFactDesc(name).

The FactHelper Mix-In Class

The FactHelper class is provided to be mixed in with TopicEntries to give them additional functionality in connection with Facts, and in particular to allow the topicResponse methods of such TopicEntries to automatically list the known Facts related to the object the TopicEntry matched on. This will be most useful with ConsultTopics and Thoughts, resulting in the FactConsultTopic and FactThought classes which are discussed further below. Here we shall simply run through the methods and properties common to both these classes via FactHelper, before explaining their use on Thoughts and Consultables below.

At first sight this may seem a rather daunting profusion of properties and methods, but for the most part you can accept the defaults defined on FactThought and FactConsultTopic, which then become very easy to define and use, although you may of course want to customize the prefix, noFactsMsg and knewFactAlreadyMsg properties to provide your own messages.

FactConsultTopic

A FactConsultTopic is simply a FactHelper mixed in with a ConsultTopic which can potentially handle all LOOK UP commands aimed at the Consultable. If we don't need to customize any of the FactConsultTopic's default messages, then defining a Consultable that can respond to a range of queries could be almost as simple as defining the Consultable object itself plus a single FactConsultTopic:

+ book: Consultable 'big red book'
    "It's a book full of miscelleanous wisdom. "        
;

++ FactConsultTopic
;

LOOK UP X IN BIG RED BOOK will then produce a sentence listing all the facts the book 'knows' about X (if it 'knows' any), or a message saying the big red book doesn't have any information on that subject if it doesn't, and that's our big red book more or less taken care of.

Except of course, we also have to define the Facts (and topics) we want out big red book to provide answers on, for example:

Fact 'spain-in-europe' [tSpain]
    'Spain is a country in Europe'
    [book]
;

Fact 'madrid-capital' [tSpain, tMadrid]
    'the capital city of Spain is Madrid'
    [book]
    
    qualifiedDesc(source, topic, narrator?)
    {
        if(topic == tMadrid)
            return 'Madrid is the capital city of Spain';
        else
            return desc;
            
    }
;

Fact 'europe-location' [tEurope]
    'Europe is a continent north of Africa, west of Asia, and east of the Atlantic. '
	[book]
;

And presumably a whole lot more. Note that the [book] at the end of every Fact definition is setting its initiallyKnownBy property; we're saying these three Facts are 'known by' (i.e. contained somewhere in the pages of) the big red book, which is therefore about to provide them. Note also that the 'madrid-capital' Fact will present the same fact slightly differently according to whether the player types ASK BOOK ABOUT SPAIN or ASK BOOK ABOUT MADRID; the latter will give the response 'Madrid is the capital of Spain.'

Note also that the FactConsultTopic also takes care of adding the Facts the player character looks up to the list of Facts the player knows about, so there's no need to include any <.reveal> tags here; it's taken care of automatically.

In addition, the FactConsultTopic also takes care of any enquiries the big red book doesn't 'know' about, by reporting that the big red book has no information on that topic, so there's no need to define any DefaultConsultTopic here.

Consultables such as books normally contain a fixed store of facts, but if we're implementing a notebook the player character can add information to or a computer database, we can expand its store of knowledge by calling its setInformed() method, e.g., setInformed('rain-in-spain') if we'd defined a 'rain-in-spain' Fact.

Finally, note that using a FactConsultTopic doesn't prevent our also populating our Consultable object with ordinary ConsultTopics as well. These will normally take precedence over our FactConsultTopic, since a FactConsultTopic has a matchscore of 50 while a regular ConsultTopic has a default matchscore of 100. You can that use ordinary ConsultTopics to field queries on topics you don't want to include in the Facts framework (though you may find it easier and more consistent to use the Facts framework for your Consultable throughout.

You may be wondering whether using a FactConsultTopic really saves you all that much work compared with using a series of ConsultTopics, since with the former you then have to define all the relevant Facts. The real gain from using a FactConsultTopic and related Facts comes when you use the same Facts elsewhere in your game (as we shall be illustrating below), since the Facts framework enables Facts to be consistently described in the different places they may be used, and helps you to keep track of what information each Fact encapsulates and of who needs what, without having to define lots of isActive properties on various TopicEntries and having to co-ordinate them all.

ConsultTopic referencing a Fact can also be defined as Short-Form ConsultTopics by using the fact's name string in place of the normal response string in a list of responses on the Consultable's topicEntryList. For example, you might have:

++ book: Consultable 'big red book'
    "It's a book full of miscelleanous wisdom. "
    bulk = 2
    
    topicEntryList = [
        ['weather', 'The weather can be difficult to predict. '],
        ['life', 'Life is generally to be preferred to the alternative. '],
        [tStairs, 'stairs-useful' ],
        [tEconomics, 'A dark art practised by sorcerers. '],        
        ['europe', 'europe-location'],
        [me, '''If you're looking for flattery you're doomed to disappointment. ''']   
    ]    
;

Fact 'europe-location' [tEurope]
    'Europe is a continent north of Africa, west of Asia, and east of the Atlantic'    
;

Fact 'stairs-useful' [tStairs, mainStairs]
    'stairs can be quite useful for connnecting floors'
    pcComment = '(talk about telling you the bleeding obvious)'
;

In this case, the responses to LOOK UP WEATHER/LIFE/ECONOMICS/ME IN BOOK would simply be the strings defined as the second entry in each sub-list (e.g., "The weather can be quite difficult to predict."), whereas the responses to LOOK UP STAIRS/EUROPE IN BOOK would the desc property of the matching Fact (with name 'stairs-useful' or 'europe-location'), with the first character raised to upper case, and a full stop appended along with an appropriate <.reveal> tag, e.g., <.reveal europe-location>. This might then generate an exchange such as the following:

Hall
From here stairs lead up to the floor above while the front door iies to the south. A passage leads north, and another room lies just to the east. 

You can see a wallet, a hat, an umbrella, a mobile phone, and a small table here.

On the small table you see a big red book.

>think about europe
Nothing relevant comes to mind. 

>think about stairs
Nothing relevant comes to mind. 

>look up europe in book
Europe is a continent north of Africa, west of Asia, and east of the Atlantic. 

>look up stairs in book
Stairs can be quite useful for connnecting floors. 

>think about europe
You recall that the big red book told you that Europe is a continent north of Africa, west of Asia, and east of the Atlantic. 

>think about stairs
You recall that the big red book told you that stairs can be quite useful for connnecting floors (talk about telling you the bleeding obvious). 

>look up life in book
Life is generally to be preferred to the alternative. 

We'll say more about where such THINK ABOUT responses coeme from immediately below. In the meantime you may be wondering how the gamne knows whether the second string element in each topicEntryList sublist is to be treated as literal text to be displayed or as the name of a Fact. The answer is it's treated as the latter if there's a Fact in the game with a name corresponding to the string, and the former otherwise.

FactThought

A FactThought is a FactHelper mixed-in with a Thought, with some of its properties suitably overridden to give useful responses to commands like THINK ABOUT SPAIN (or whatever). Such commands will then list all the Facts the player character knows about the relevant topic, together with the sources of that information and (if any such sources are listed) if the player character also started out knowing about that topic in any case. A FactThought does not, however, update the player character's state of knowledge, since it's assumed that thinking about something the player character already knows does not alter what s/he knows.

Just for the sake of illustration, suppose the only thing our Player Character starts out 'knowing' about Spain (or the weather) comes from one of the songs in My Fair Lady:

Fact 'rain-in-spain' [tWeather, tSpain]    
    'the rain in Spain stays mainly in the plain'    
    [me]    
    
    priority = 110
;

Suppose next we define a ThoughtManager (to hold the player character's thoughts) and we define just one regular Thought plus a FsctThought:

myThoughts: ThoughtManager;

+ Thought 'life'
    "You've always considered it preferable to the alternative. "
;

+ FactThought
;

Suppose finally that our player character is carrying the big red book we used to illustrate the use of FactConsultTopic. We might then get the following transcript from our minimally implemented game:

>think about life
You’ve always considered it preferable to the alternative. 

>think about bananas
Nothing relevant comes to mind. 

>think about spain
You recall that the rain in Spain stays mainly in the plain. 

>think about weather
You recall that the rain in Spain stays mainly in the plain. 

>think about madrid
Nothing relevant comes to mind. 

>look up spain in book
The big red book informs you that the capital city of Spain is Madrid and that Spain is a country in Europe. 

>think about spain
You recall that:
The rain in Spain stays mainly in the plain.
The big red book told you that the capital city of Spain is Madrid.
The big red book told you that Spain is a country in Europe.

>think about madrid
You recall that the big red book told you that Madrid is the capital city of Spain. 

>think about europe
Nothing relevant comes to mind. 

This is just a toy example, of course, but it illustrates what can be done with a FactThought and also the potential saving of effort once different parts of the Facts framework start to be used together. In the previous section we defined a Consultable and small collection of Facts it can provide information on. With that done, we have very little work to do to enable the player character to recall what s/he has learned from the Consultable; the single FactThought has pretty much done all the work for us.

There are, however, a few more points to note here:

The Player Character's Comments on Facts

The FactThought does a good job of listing what the player character knows about a given topic, but there's one more customization we can make if we wish, and that is to append the player's comments on any fact which a FactThought list, so that, for example, instead of:

>think about spain
You recall that:
The rain in Spain stays mainly in the plain.
The big red book told you that the capital city of Spain is Madrid.
The big red book told you that Spain is a country in Europe.

You could get:

>think about spain
You recall that:
The rain in Spain stays mainly in the plain — or so the song goes.
The big red book told you that the capital city of Spain is Madrid.
The big red book told you that Spain is a country in Europe — not that you’ve ever been to Europe.

To facilitate setting this up, the following additional properties/methods are defined on Fact:

And on factManager:

The factManager methods merely call the equivalent methods on the relevant Fact. They are provided so that Player Character comments can be set or retrieved in one statement instead of first having to call gFact(tag) to retrieve the fact, then checking that the retrieved fact is not nil, and then calling the appropriate method on that fact.

To achieve the results in the example above, we'd simply define the initial player character's initial comments on the relevant facts:

Fact 'rain-in-spain' [tWeather, tSpain]    
    'the rain in Spain stays mainly in the plain'    
    [me]    
    
    pcComment = '--- or so the song goes'
;

Fact 'spain-in-europe' [tSpain]
    'Spain is a country in Europe'
    [book]
    
    pcComment = '--- not that you\'ve ever been to Europe'
;

If the player character's thoughts on any given fact subsequently change, they can be set (or reset) using factManager.setPcComment(tag, txt) or calling the fact's setPcComment(actor, txt) method. These methods can also be used to set the comments of a new player character should the player character change in the course of the game.

Facts and Conversation

While you're free to experiment with mixing in the FactHelper class with various kinds of ActorTopicEntry, and this may be fine if the NPC is meant to be a robot (or someone with a peculiarly robotic personality), it is likely to come over as rather too wooden for most human NPCs. The Facts module therefore provides various modifications to the TopicEntry and ActorTopicEntry classes to allow them to be used with Facts in a more flexible way more suited to the flow of a conversation. The former set of modifications can be used for games where the author wants the flexibility of using Facts to provide (part of) the text of conversational responses without having to worry about which Facts are known by which NPCs or which topics any given Fact is associated (after all, an NPC might choose to respond to an enquiry about one subject by citing a fact about some completely different subject. The latter set can be used for games where you're happy to keep track of NPC knowledge (including what NPCs start out by knowing) and to keep discussion of facts strictly confined to the topics they relate to, in return for the library doing a bit more of the other work for you. Yo are, of course, free to mix and match both approaches in your game if that works for you.

The additional methods defined on TopicEntry are:

For example, using the first and last of these methods, we could define a TopicEntry thus:

+ AskTopic, StopEventList @tWeather    
    [
       '<q>\^<<revealFact('rain-tomorrow')>></q> Bob warns you. ',        
       'Bob has already told you <<factText('rain-tomorrow')>>. '        
    ]       
;

The additional methods defined on ActorTopicEntry are:

(The names aTag and tTag were chosen to suggest asking and telling, but if you prefer you can use the names rTag and iTag respectively to suggest reveal and inform, thus matching the names of the revTag() and infTag() methods; rTag and iTag are simply macros which translate to aTag and tTag respectively.)

Armed with these additional resources we could implement the previous example thus:

+ AskTopic, StopEventList   
    [
        '<q>\^<<revTag(),>></q> Bob warns you. ',
        'Bob has already told you <<fText()>>. '
        
    ]
    aTag = 'rain-tomorrow'
;

Suppose that in addition to the previous example Facts, and an actor called Bob, we have also defined:

Fact 'rain-tomorrow' [tWeather]
    'it will rain tomorrow'    
    [bob]
    
    qualifiedDesc(source, topic, narrator?)
    {
        if(source == bob)
            return 'it\'ll rain tomorrow';
        else
            return desc;;            
    }
;


Then, with the AskTopic as defined above (plus a suitable HelloTopic on Bob) we should get:

>think about weather
You recall that the rain in Spain stays mainly in the plain — or so the song goes. 

>ask bob about weather
“Hello, Bob,” you say.

“Hello, you,” he replies. 
“It’ll rain tomorrow,” Bob warns you. 

>think about weather
You recall that:
The rain in Spain stays mainly in the plain — or so the song goes.
Bob told you that it will rain tomorrow.

One kind of ActorTopicEntry the Facts module makes special provision for is the SayTopic. The standard way of using a SayTopic is illustrated in the following example:

+ SayTopic 'you\'re not afraid of the dark tower; you are i\'m i am'
  "<q>I'm not afraid of the dark tower, you know,</q> you boast.\b
   <q>Well, you should be,</q> Bob warns you. "
;

Now suppose we'd like to enapsulate 'you're not afraid of the dark tower' into a Fact which Bob is informed of when this SayTopic is triggered:

Fact 'not-afraid' [tTower]
    'you\'re not afraid of the dark tower'
    [me]	
    qualifiedDesc(source, topic, narrator?)
    {
        if(narrator == speaker)
            return 'i\'m not afraid of the dark tower';
        else 
            return desc;
        
    }
;

We could then re-write our SayTopic as:

+ SayTopic 'not-afraid' 'you are i\'m i am'    
    "<q>\^<<infTag()>>, you know, </q> you boast.\b
     <q>Well, you should be,</q> Bob warns you. "
;

The template we're employing here is:

SayTopic template
    +matchScore?
    'tTag' 'extraVocab'
    "topicResponse" | [eventList] ?;

So in our example, 'not-afraid' is the fact tag for the Fact we want to use and 'you are i\'m i am' is the extraVocab we need so that our SayTopic will match commands like I AM NOT AFRAID as well as SAY YOU'RE NOT AFRAID. If you use this template, this second element must be present, even if it's only the empty string '' (otherwise the compiler won't know which template it's meant to be matching). What happens here is that at preinit, our SayTopic sets its name to gFact(tTag) and then adds '; ' + extraVocab to the name to create the vocab the SayTopic matches.

The Facts module also makes a small change to InitiateTopic to allow TopicEntries of this class to match fact name tags (as well as Things, Topics and matchPatterns). This would allow us to define an InitiateTopic such as the following:

+ InitiateTopic 'jumping-silly'
    topicResponse()
    {
        "<q>\^<<revTag()>>,</q> says Bob. ";
    }
;

Fact 'jumping-silly' [tJumping]
    'jumping is silly'
    [bob]
;

We can then trigger this response with a call to bob.initiaeTopic('jumping-silly'), for example:

bob: Actor 'Bob;;;him' @lounge
    actorAfterAction()
    {
        if(gActionIs(Jump))
            initiateTopic('jumping-silly');
    }
;

If the revealing property of such an InitiateTopic is true (as it is by default), its aTag property will be set to the fact name just matched, so that we can use the revTag() method as in the example above. The thinking here is that setting revealing to true implies that the InitiateTopic is stating a Fact which is accordingly revealed to the player character. If, however, the InitiateTopic is asking a question, we should set revealing to nil, since asking a question does not convey any facts (in the sense of this Fact module). Whether revealing is true or nil, providing that there is a fact name corresponding to the string the InitiateTopic matches, its topicMatched property will be set to that corresponding fact.


Contextualising qualifiedDesc

We have already seen how we can use the qualifiedDesc() method to adjust the description of a fact to its context, but there is one common case the library's definition of qualifiedDesc() can handle for us, provided we define our desc in a way that makes it clear what we want it to do.

Suppose we have defined a Fact such as the following:

Fact 'bob-likes-cousin' [tCousin]
    'Bob likes his cousin'
    [bob]
;

Together with an AskTopic for bob such as:

++ AskTopic @tCousin
    "<q><<revealFact('bob-likes-cousin')>>,</q> Bob tells you. "
    
    name = 'his cousin'    
;

Without any further adaptation, this would give us:

>ask bob about cousin
“Bob likes his cousin,” Bob tells you.

>think about cousin
You recall that Bob told you that Bob likes his cousin.

Which probably isn't what we want. But rather than diving into qualifiedDesc() to try to arm-wrestle it into producing something more appropriate, we can instead leave qualifiedDesc() as it is and instead change the way we write our Fact's desc:

Fact 'bob-likes-cousin' [tCousin]
    '[bob] like{s/d} [his] cousin'
    [bob]
;

We'll then get:

>ask bob about cousin
“I like my cousin” Bob tells you.

>think about cousin,
You recall that Bob told you that he likes his cousin.

Tbis works by means of qualifiedDesc() working out the context of the utterance (e.g., whether it's the subject of the sentence, here Bob, who's speaking or whether what's being reported is what he told you before, e.g. 'Bob told you that') and then replaces the square-bracketed text (here '[bob]' and '[his'] with the message parameter needed to produce the text we want. We don't need to know the details of how this works to use it. The important points to remember are:

  1. The first square-bracketted expression you use must be the globalParamName of the Actor in question in square brackets, e.g. '[bob]'. Note that the library automatically assigns the programmatic name of an Actor to its globalParamName name unless you decide to give it different one, so unless you change it, the globalParamName of bob will be 'bob'.
  2. Any subsequent square-bracktted text you use should be one of the message parameters listed later in this manual, minus the obj part, e.g. '[his]' rather than '[his bob]' or '[his obj']; the qualifiedDesc method will then work out what to supply here.
  3. To secure subject-verb agreement you should use an ordinary message parameters, e.g. 'like{s/d}' rather than just 'likes'.

Debugging Commands

The Facts module provided a couple of debugging commands which are available when your game has been compliled for debugging.

LIST FACTS displays a list of all the facts in the game, showing their (string) name and their desc, sorted in alphabetical order of name.

FACT INFO name displays information about the Fact identified by name, e.g.,:

>fact info rain-in-spain
Name = rain-in-spain
Desc = the rain in Spain stays mainly in the plain
Topics = [weather, Spain]
Initially Known By = [you]
Currenly Known By = [you]