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:
- Fact: objects of this class encapsulate facts along associated with the objects they concern and the people that know about them.
- factManager: the object that keeps track of which Fact is associated with which tag and provides several service methods.
- FactHelper: a mix-in class to provide additional Fact-related functionality to various kinds of TopicEntry.
- FactConsultTopic: A ConsultTopic incorporating the additional functionality provided by FactHelper.
- FactThought: A Thought incorporating the additional functionality provided by FactHelper.
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:
- name: A single-quoted string containing the name of this Fact, which must correspond with the name in any <.reveal name> or <.inform name> tag and the like that references this fact.
- desc: A description of what this Fact asserts, as a single-quoted string that could follow the word 'that', without any closing punctuation, e.g. 'it will rain tomorrow' or 'the capital of Spain is Madrid' or 'King Richard III was killed at the Battle of Bosworth in 1485'. If developments in your game make it appropriate to change the description of a fact, you can do so with the redescFact(name, newdesc) function, where name is the name of the Fact whose desc you want to change and newdesc is its new description./li>
- qualifiedDesc(source, topic, sender): an alternative description of what this Fact asserts when it is asserted by source and/or in relation to topic, although this alternative description must say essentially the same thing as desc (which is returned by default). For example, if the topic were 'Madrid' rather than 'Spain', qualifiedDesc might return 'Spain is the capital of Madrid' rather than 'the capital of Madrid is Spain'. The sender parameter gives the identity of the object that called this method, which can be used to help contextualize the text we wantit to display.
- topics: The list of topics (Topics and Things, i.e. game objects) that this Fact relates to. One use of this is to cause this Fact to be listed in response to THINK ABOUT topic or LOOK UP topic IN whatever.
- initiallyKnownBy: The list of actors and other objects - typically Consultables - that start the game knowing this Fact. For Facts, it is preferable to define this on the Fact rather than the objects' initiallyKnowsAbout property.
- currentlyKnownBy: returns a list of game objects (typically actors and Consultables) who currently know this Fact.
- priority: A measure of the importance of this Fact. The higher the priority, the earlier it will be listed in any list of Facts. The default priority is 100.
- listOrder: An alternative or additional way of determining the order in which Facts are listed. The lower the listOrder, the earlier in any list this Fact with appear.
- adjustedPriority: The number that's actually used to sort the listing order of Facts; by default it's defined as priority - listOrder. So, for example, you could set priority in multiples of 100 to band Facts by relative importance and use smaller integer values of listOrder to order those Facts within their priority bands.
- getSources(): returns a list of the actors and/or Consultables who have imparted this fact to the current player character.
- getTargets(): returns a list of the actors and/or Consultables (notebooks, perhaps?) to whom the current player character has imparted this fact.
- pcComment, getPcComment(source, topic) and setPcComment(actor, txt) will be explained below in relation to FactThought.
- defaultTruthValue: the truth value that most characters in this game who know of this fact are likely to give it. The default is true. Other possible values are likely, dubious, unlikely and untrue. This can be overridden for individual actors in our initiallyKnownBy list by using a two-element list for them in place of just the actor, e.g. [thomas, dubious] instead of just thomas.
- relevant: flag; is this Fact still relevant? By default this is true for all Facts, but it could be set to nil on a Fact that has ceased to be relevant due to developments in the game; you could, for example, define this property as an expression that changes from true to nil when a condition is met that renders the Fact obsolete. This is principally for use with FactThought.
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:
- getFact(name): this returns the Fact object whose name is name.
- getFactDesc(name): this returns the desc (fact description) of the Fact object whose name is name.
- getQualifiedFactDesc(actor, tag, topic, sender): this returns the qualifiedDesc of the Fact object whose name is name when the source or actor that imparted the fact to the player character is actor and the description has been requested in conjunction with topic, e.g. THINK ABOUT SPAIN. The sender argument will normally selfy
- getPcComment(tag, topic) and setPcComment(tag, txt) will be explained below in relation to FactThought
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 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. 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:The FactHelper Mix-In Class
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 It comes to mind 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 It comes to mind 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 It comes to mind that the rain in Spain stays mainly in the plain. >think about weather It comes to mind 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 It comes to mind 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 It comes to mind 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:
- Any regular Thought (such as the one about life in our example) takes precedence over our FactThought (since a FactThought has a matchScore of 50 rather than the default 100.
- Since some if not many of the player character's thoughts may be about things other than facts they know or have learned, such as plans, feelings, opinions, suspicions and the like, you may well need to implement a number of Thought objects as well as the FactThought that deals with the factual topics (as well as acting as catch-all default for topics that haven't been implemented).
- You may, of course, wish to tailor the catch-all default response (by overriding the noFactsMsg on the FactThought), but it's best not to do so with any response implying the player character's ignorance of the topic (otherwise you risk getting incongruous responses to commands like THINK ABOUT YOUR MOTHER or THINK ABOUT YESTERDAY).
- Dont't forget that you have considerable control over how a FactThought presents its responses through the various properties defined on FactHelper. If you don't want it to list the sources of information, or to list each fact on a separate line, or you'd rather the facts were introduced by 'It occurs to you that' rather 'You recall that', these are all things you can customize.
- It's possible that developments in a game/work of IF render some Facts no longer relevant; e.g., if the response to THINK ABOUT MAVIS includes 'Mavis wants you to retrieve a letter', it could well seem redundant to list this once the letter has been retrieved. One way to deal with this would be to set relevant to nil on the Fact in question (or define its relevant property to evaluate to nil once Mavis is in possession of the letter. The other is to change the description of the fact by using the redescFact(tag, newdesc) function e.g., redescFact('retrieve-letter', '[mavis] needed you to retrieve a letter, but you\'ve done that now').
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 It comes to mind 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 It comes to mind 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:
- pcComment: A single-quoted string containing the initial player character's initial comment or thought on this Fact; this can be left at nil if the PC doesn't have one. This will be appended to the description of this Fact when listed by a Thought, so should be a sentence fragment starting with a lower case letter (or some form of parenthetic punctuation) and without a full stop at the end.
- getPcComment(source, topic): Get the current player character's comment on this Fact; source is the source from which the PC learned the Fact and topic is the topic the Player Character is thinking about. By default this method returns different results for different player characters, but game code will need to override this method to return different comments for different sources and/or topics.
- setPcComment(actor, txt): Set actor's comment on this fact; normally actor will be the current player character; txt is a single-quoted string containing the comment, which will usually be appended to the description of the fact.
Note that in some circumstances the library may append a comment if you don't define one yourself. In particular if the player character regarda a Fact not as probable, dubious, improbable or untrue the FactHelper class will append a notice to that effect.
And on factManager:
- getPcComment(tag, topic): Get the player character's comment on the fact whose name is tag when it is retrieved in relation to topic (typically by a THINK ABOUT topic commannd).
- setPcComment(tag, txt): Set the current player character's comment on the Fact identified by tag; txt is a single-quote string containing the comment.
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:
- revealFact(tag, msg?): This method displays the desc of the Fact referenced by tag as described by the NPC the player character is in conversation with in relation to the topic matched, adds the PC's conversation partner to the list of sources for this fact, and adds the fact's name tag to the list of topics the Player Character knows about. Note that it does not check whether the PC's current interlocutor knows this Fact or whether this Fact is related to the current topic of conversation.
- informFact(tag, [actor], msg?): This method displays the description of the Fact referenced by tag as described by actor in relation to the topic matched, adds the PC's conversation partner to the list of targets for this fact, and adds the fact's name tag to the list of topics actor knows about. Note that it does not check whether the Player Character knows this Fact or whether this Fact is related to the current topic of conversation. The second,
actor parameter is optional and defaults to the player character's current interlocutor (the normal case) if not supplied. - factText(tag, [actor]): This method simply displays the described of the Fact referenced by tag as described by actor in relation to the topic matched. The second,
actor parameter is optional and defaults to the player character's current interlocutor (the normal case) if not supplied.
The function of the optional msg parameter will be discussed below, in the context of explaining how the use of Facts in conjunction with TopicEntries can be made more flexible.
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:
- aTag: the knowledge tag (fact name string) associated with this ActorTopicEntry when the player character is asking the NPC something (i.e. on an AskTopic or QueryTopic). The ActorTopicEntry will then match the topics associated with this fact, but will only be active (and hence accessible) if the current interlocutor knows the fact in question. Note that we can still override the TopicEntry's matchObj to match some other topic or list of topics if we wish, although this may risk defeated the object.
- tTag: the knowledge tag (fact name string) associated with this ActorTopicEntry when the playerChar is telling the NPC something (i.e. on a TellTopic or SayTopic). The ActorTopicEntry will then match the topics associated with this fact, but will only be active (and hence accessible) if the current interlocutor knows the fact in question. Note that we can still override the TopicEntry's matchObj to match some other topic or list of topics if we wish, although this may risk defeated the object. Note also that it makes no sense to define both aTag and tTag on the same TopicEntry; if aTag is non-nil tTag will be ignored.
- revTag(msg?): calls revealFact(aTag) --- for which see above --- and returns its return value, which can then be used to construct this ActorTopicEntry's topicResponse. This would typically be used on an AskTopic or QueryTopic
- infTag(msg?): calls informFact(tTag) --- for which see above --- and returns its return value, which can then be used to construct this ActorTopicEntry's topicResponse. This would typically be used on a TellTopic or SayTopic<
- fText(): returns the value of factText(aTag ?? tTag), in other words factText(aTag) if aTag is not nil and factText(tTag) otherwise.
(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:
- 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'.
- 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.
- To secure subject-verb agreement you should use an ordinary message parameters, e.g. 'like{s/d}' rather than just 'likes'.
Some More Flexible Ways of Using Facts with TopicEntries
The ways of using Facts in conjunction with TopicEntries we have seen so far require Fact objects to be defined separately from the TopicEntries that use them. Since these Facts may well be used for other purposes as well, such as supplying responses to THINK ABOUT commands, this may seem fair enough, and some game authors may welcome the clean separation of function here. But others may prefer to be able to customize conversational responses entirely on the relevant TopicEntries rather than also having to tweak code on any Fact object they reference, and some may even find it neater and easier not to have to define the Fact separately from the TopicEntry that references it, especially if the Fact is relevant specifically to that conversational exchange. Adv3Lite therefore makes it possible to customize how a Fact is articulated from within a TopicEntry and to create a new Fact on the fly from within a TopicEntry.
You may recall that several of the TopicEntry and ActorTopicEntry methods described above have an optional msg parameter; this can be used to customize how the description of the Fact is displayed in the context of the TopicEntry's response. The methods in question are:
- revealFact(tag, msg?): This method displays the desc of the Fact referenced by tag as described by the NPC the player character is in conversation with in relation to the topic matched, adds the PC's conversation partner to the list of sources for this fact, and adds the fact's name tag to the list of topics the Player Character knows about. Note that it does not check whether the PC's current interlocutor knows this Fact or whether this Fact is related to the current topic of conversation.
- informFact(tag, [actor], msg?): This method displays the description of the Fact referenced by tag as described by actor in relation to the topic matched, adds the PC's conversation partner to the list of targets for this fact, and adds the fact's name tag to the list of topics actor knows about. Note that it does not check whether the Player Character knows this Fact or whether this Fact is related to the current topic of conversation. The second,
actor parameter is optional and defaults to the player character's current interlocutor (the normal case) if not supplied. It can be omitted even if msg is supplied; in other words it's perfectly fine to call this method as informFact(tag, msg) - revTag(msg?): calls revealFact(aTag, msg) --- for which see above --- and returns its return value, which can then be used to construct this ActorTopicEntry's topicResponse. This would typically be used on an AskTopic or QueryTopic
- infTag(msg?): calls informFact(tTag, msg) --- for which see above --- and returns its return value, which can then be used to construct this ActorTopicEntry's topicResponse. This would typically be used on a TellTopic or SayTopic.
In turn, the msg parameter can be one of:
- A single-quoted string (to be used as a description of the fact).
- An integer (indexing the fact's factDescs list)
- A property pointer, in which case the corresponding method will be called on the fact with self as argument. The method should return a single-quoted string which will be used as the fact's description.
- A function pointer, in which case the function will be executed with self as an argument. The function should return a single-quoted string which will be used as the fact's description.
The first of these is probably the most straightforward, and along with the fourth, which is provided for more elaborate cases, allows the definition of what's displayed by the TopicEntry to be kept within the definition of the TopicEntry. The second and third uses of the msg parameter conversely allow how the Fact is described to be kept along with the definition of the Fact. Both methods have their advantages and disadvantages so it is us to game authors to decide which they prefer.
Some game authors might quite reasonably prefer to define everything in one place rather than having to define Facts separately from the TopicEntries that reference them. You can do this through the following pair of TopicEntry methods:
- revealNewFact(name, desc, msg?, topics?, initiallyKnownBy?): Creates a new Fact defined by the name, desc, topics and initiallyKnownBy parameters and reveals it to the player character.
- informNewFact(name, desc, msg?, topics?, initiallyKnownBy?, actor?): Creates a new Fact defined by the name, desc, topics and initiallyKnownBy parameters and informs actor of it.
Here the name, desc, topics and initiallyKnownBy parameters refer to the corresponding properties on the new Fact being created. If not explicitly supplied, topics defaults to the matchObj of the TopicEntry on which one or other of these methors is called. initiallyKnownBy defaults to getActor() (i.e. the actor whose TopicEntry this is) in the case of revealNewFact() and gPlayerChar in the case of informNewFact(). The actor parameter also defaults to getActor, while the msg paramenter has the same meaning as just described above. Note that if any of the optional parameters is supplied, all those to the left of it must be also be supplied, so, for example, if you wish to speficy the topics parameter you must also supply the msg parameter, which can just be nil if you don't wish to use it.
If you prefer brevity to explicit clarity in using these methods on TopicEntries you can abbreviate informNewFact() to INF() and revealNewFact() to RNF().
What these two methods do is first check whether a Fact corresponding to name already exists and, if not, calls the Fact constructor to create the new Fact defined by the parameters supplied (amongst other things, this makes it safe to call INF() or RNF() more than once even it may potentally refer to the same Fact). Then, whether or not a new Fact has just been created, INF() goes on to call informFact(name, actor, msg) and RNF() to call revealFact(name, msg).
The Fact constructor just referred to is defined on the Fact class as construct(name_, desc_, topics_, initiallyKnownBy_). It creates a new Fact object and sets the corresponding properties, initializes the new Fact and then adds it to the table of Facts on the factManager object. This ensures that a reference to the new Fact continues to exist; the new Fact can then be retrieved/referenced using gFact(name), and will show up in response to the LIST FACTS and FACT INFO commands described below.
So, for example, if we were creating some TopicEntries for a subsequent conversation between the player character and Mavis, assuming we give Mavis an ActorState for this conversation we might define:
++ TellTopic @tSpain "<q>You know, they say <<informFact('rain-in-spain', 'the wet stuff falling from Spanish skies tends to end up on the plain')>>,</q> you say.\b <q>That doesn't set to music so well as the original,</q> she complains. " ; ++ AskTopic @tMadrid "<q>What do you think of Madrid?</q> you ask.\b <q><<RNF('madrid-ok', 'Madrid\'s okay', 'It\'s okay')>>, I guess,</q> she replies. " autoName = true ;
Which could then lead to the following exchange>
>t spain “You know, they say the wet stuff falling from Spanish skies tends to end up on the plain,” you say. “That doesn’t set to music so well as the original,” she complains. >a madrid “What do you think of Madrid?” you ask. “It’s okay, I guess,” she replies. >think about madrid You recall that Mavis told you that Madrid’s okay.
Alterrnatively, we could customise the rain-in-spain quip on the Fact object and reference it from the TopicEntry in one of two ways. The first uses the msg parameter to index the factDescs property on the Fact:
++ TellTopic @tSpain "<q>You know, they say <<informFact('rain-in-spain', 1)>>,</q> you say.\b <q>That doesn't set to music so well as the original,</q> she complains. " ; Fact 'rain-in-spain' [tWeather, tSpain] 'the rain in Spain stays mainly in the plain' [me, bob] priority = 110 factDescs = ['Spanish rain descends principally on level places'] ;
The second uses the msg parameter to call a method on the Fact object:
++ TellTopic @tSpain "<q>You know, they say <<informFact('rain-in-spain', &altDesc)>>,</q> you say.\b <q>That doesn't set to music so well as the original,</q> she complains. " ; Fact 'rain-in-spain' [tWeather, tSpain] 'the rain in Spain stays mainly in the plain' [me, bob] pcComment = '--- or so the song goes' priority = 110 altDesc(sender) { return 'rain south of the Pyrenees gets deposited on low-lying ground'; } ;
The method you call here doesn't have to be called altDesc; it can be called anything you like. Its sender parameter is the TopicEntry from which it was called, whose various properties (such as matchObj, topicMatched, getActor and narrator (the actor imparting the fact) can be queried to tailor the response to the context. While you could achieve the same result by overriding qualifiedDesc(), you may find it easier to keep your customization separate from the libary's definition of qualifiedDesc(), which, as we have seen, provides its own means of adapting the Fact's description to its context.
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.On an HMTL-enabled interpreter that fact names will be hyperlinked, so that clicking on one of them will execute the FACT INFO command for that Fact.
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 = [tWeather, tSpain] Initially Known By = [me, bob] Currently Known By = [mavis, me, bob] Adjusted Priority = 109 pcComment = '— or so the song goes' Targets = [mavis] FactDescs = ['Spanish rain descends principally on level places']