Player Character and NPC Knowledge

One type of condition that clearly effects what conversation topics should be available at any point in time is what the player character, and possibly the NPC s/he's conversing with, knows at that point in time. For example the player character can't ask Bob about the dark tower until the player character knows such a thing exists (even if the player knows it because s/he's played the game before). In this section we look at how to keep track of what the player character and other characters know, and how to use that information to affect what conversation topics are available at any point in time.

The Knowledge Model

The adv3lite knowledge model starts from the premise that the player character knows of the existence of an object either if s/he has seen it, or if we declare it to be familiar. The familiar property is a way of telling the game that the player character knows about (i.e. knows of the existence of) an object even is s/he hasn't seen it (or hasn't seen it yet). This allows the player to ask or tell other characters about this object.

The library attempts to keep track of what the player character has seen by setting the seen property of objects to true once the player character has seen them. In some cases, however, a game author may have to do this manually, for example when moving an object into view using moveInto(). For reasons that will become more apparent below, rather than setting the value of the seen property manually, it's often best not to do so directly (which a statement like codebook.seen = true) but either by calling the setSeen() method on the object that has just come into view (e.g. codebook.setSeen()) or calling setHasSeen(obj) on the player character (e.g. gPlayerChar.setHasSeen(codebook)). The second method can be abbreviated by using the gSetSeen() macro; writing gSetSeen(codebook) is exactly equivalent to writing gPlayerChar.setHasSeen(codebook). To test whether the player character has seen something we can use the hasSeen() method, e.g. gPlayerChar.hasSeen(codebook) will be true if the player character has seen the codebook.

In the case of Things (or Topics) the player character learns about other than by actually seeing them, we use the familiar property. If the player character learns of the existence of a codebook that s/he is yet to find, we can set codebook.familiar to true to indicate that the player character now knows of the codebook's existence (and so can refer to it in conversation). Again it's often best to do this using either setKnown(), e.g. codeBook.setKnown() or setKnowsAbout(), e.g. gPlayerChar.setKnowsAbout(codebook) which can be abbreviated to gSetKnown(codebook) using the gSetKnown() macro. Each of these methods will set codeBook.familiar to true.

Remember, the player character knows about the codebook either if s/he has seen it (codebook.seen is true) or if s/he has learned about its existence in some other way (codebook.familiar is true). To test whether the player character knows about the codebook we use the knowsAbout() method, e.g. gPlayerChar.knowsAbout(codebook) or the known property, e.g. codebook.known (which simply returns the value of gPlayerChar.knowsAbout(codebook)).

At first sight, this may all seem unnecessarily complex; why not just set and test the values of seen and known directly, and why do we need another property called familiar? To answer the second question first we need to distinguish familiar (known about but not necessarily seen) from known (either seen or familiar). Since obj.known is true either if obj.seen is true or obj.familiar is true, we need three property names to keep track of the epistemological model.

In games where we're only interested in what the player character has seen and knows about, it would be perfectly okay to set and test the values of seen and familiar directly. The complication comes in either when we have more than one player character (because the player character changes during the course of play) or because we want to keep separate track of what non-player characters have seen and/or know about. By themselves the seen and familiar properties of objects don't tell us who has seen them or is familiar with them. So if we're interested in tracking the knowledge of more than one actor in the game we have to do a bit more work.

To this end we can use the seenProp and knownProp properties. These are defined on the actor concerned, and should contained property pointers defining which properties of Thing (and Topic) to use to keep track of what this particular actor has seen and is familiar with. By default seenProp is just &seen for every actor, and knownProp is &familiar for every actor, meaning that calling setHasSeen(obj) on any actor will set obj.seen to true, and calling knowsAbout(obj) on any actor will set obj.familiar to true. If, however, we wanted to keep separate track of Bob's knowledge we could define new bobSeen and bobFamiliar properties on Thing (and a new bobFamiliar property on Topic), and then set bob.seenProp to &bobSeen and bob.knownProp to &bobFamiliar, like this:

modify Thing
    bobSeen = nil
    bobFamiliar = nil
;

modify Topic
   bobFamiliar = true // We tend to assume that most topics start out familiar, but we don't have to. 
; 

bob: Actor 'Bob; squat little;fellow man; him'
   "He's a squat little fellow. "
   seenProp = &bobSeen
   knownProp = &bobFamiliar
;
 

With these definitions in place, bob.setHasSeen(obj) will set obj.bobSeen to true, and bob.setKnowsAbout(obj) will set obj.bobFamiliar to true. Likewise, bob.hasSeen(obj) will return the value of obj.bobSeen, and bob.knowsAbout(obj) will return the value of (obj.bobSeen || obj.bobFamiliar). Note, however, that doing all this does not cause the library to keep automatic track of what Bob has seen; it is still up to the game code to call bob.setHasSeen(whatever) as and when appropriate.

If you are certain that your game will never need to keep track of what anyone other than its player character has seen and knows about (and your game will only ever have one player character), then it is safe to set the familiar and seen properties of Things and to query the known and seen properties of Things directly. Otherwise its better to use setSeen() and the rest from the start to avoid hard-to-find bugs later.

Knowledge of Existence in Conversation

We have already seen that the library keeps basic track of the player character's knowledge of the existence of things and topics. As stated aboved, we can test whether the player character knows of the existence of some object obj by testing the value of gPlayerChar.knowsAbout(obj) or, equivalently, obj.known. If obj is a Thing this will be true either if the player character has seen obj or if the familiar property of obj (i.e. obj.familiar) is true. Since Topics can't be seen they're simply known if they're familiar. We can make an object become familiar to the player character during the course of play by calling gSetKnown(obj). Equivalently, if something becomes known to the player character during the course of conversation, we can use the <.known obj> tag to do the same thing.

The important thing to bear in mind is that the matchObj property of a TopicEntry won't be matched by a conversational command if the object in question isn't known to the player character. Thus, without our having to do anything else about it, the player character can't ask about the dark tower, for example, until s/he knows of the dark tower's existence. That means we can safely define ActorTopicEntries like this:

+ AskTellTopic @tTown
   "<q>This seems a nice town,</q> you remark cheerily.\b
    <q>Yes it is -- provided you don't go anywhere the dark
    tower,</q> he agrees cautiously. <.known darkTower> "
;

+ AskTopic @darkTower
  "<q>What can you tell me about the dark tower?</q> you ask.\b
  Bob rolls his eyes and looks away for a moment. <q>You don't want
  to know,</q> he mutters. <q>You really don't.</q> "
;

Here, the second TopicEntry can't be reached until the player character knows about (i.e. knows of the existence of) the dark tower. This might be because the player character has already encountered the tower in his travels, or because he's read about it somewhere, or because some other NPC has mentioned it to him, but if the player character hasn't heard about the dark tower before the start of his conversation with Bob, he won't be able to trigger the second response until Bob has mentioned the dark tower in the first.


Knowledge of Facts

The player character's knowledge or ignorance of the existence of various things and topics may not, however, be sufficient to model all the epistemic conditions that determine what topics the player character can talk about. For example, in addition to learning of the dark tower's existence, the player character may come to learn when it was built, or that it's believed to be haunted by the ghost of a pirate, or that people are said to have mysteriously disappeared there. To represent such facts (or putative facts) we can use short string tags (fact tags), such as 'when-tower-built' or 'tower-haunted' or 'tower-disapperances'. For reasons that will shortly become apparent, we should regard such tags as inhabiting a global namespace (in other words, we should always use the same knowledge tag with the same meaning throughout our game).

When a piece of information becomes known to the player character, we can note the fact by using a <.reveal> tag; e.g. <.reveal when-tower-built> or <.reveal tower-haunted> or <.reveal tower-disappearances> (we can also do the same thing using the gReveal(tag) macro, e.g. gReveal('when-tower-built')). To test whether some item of knowledge has been revealed to the player character we can use the gRevealed(tag) macro, e.g. gRevealed('tower-haunted'). So, for example, we might go on to define:

+ AskTellTopic +110 @darkTower
   "<q>I've heard it said that the dark tower is haunted,</q> you remark.
    <q>Do you believe that?</q>\b
    Bob gives a little shudder. <q>I don't <i>believe</i> that tower is
    haunted,</q> he tells you, <q>I <i>know</i> it is!</q> "
    
    isActive = gRevealed('tower-haunted')
;

Note that we've given this AskTellTopic a matchScore of 110 (10 higher than the default) so that if the player character has heard about the rumours of haunting he asks this more specific question about the tower in place of the more general one above.

Some game authors may wish to use this mechanism for other purposes outside strictly conversational contexts as a convenient method to set and unset flags. For this purpose the library defines an <.unreveal> tag, which undoes the effect of <.reveal> and a gUnreveal(key) macro which undoes the effect of gReveal(key) [Strictly speaking, <.unreveal key> does not undo all the effects of <.reveal key>, it simply removes key from, the LookUpTable of stored keys; it doesn't undo the updating of the informed status of key on every NPC in earshot, since it is unclear that this is what should happen; game code can override Actor.setUnrevealed(tag) if it wants to handle things differently].

At this point we might also note that gReveal(), gUnreveal and gRevealed() are defined in the core library, so you can use them even if the actor.t module isn't present (e.g. for use with the hints system), whereas the <.reveal> tag, in common with all such converational tags, only works if actor.t is included in the build.

In the adv3Lite library <.reveal> tags should be regarded as a mechanism for keeping track of what has been revealed to the player character. To keep track of what the player character has told one or more NPCs we use <.inform> tags (note, the adv3 library does not make this distinction, but it seemed a useful distinction to introduce into adv3Lite), e.g., <.inform tower-haunted>. We can test whether an NPC has been informed about a particular subject by calling the informedAbout(key) method or knowsAbout(key) method on the actor, or using the gInformed(key) macro on a TopicEntry, ActorState or AgendaItem belonging to the actor. For example, if at some later point in the game the player character were to discuss his experiences in Bob's town with a friend, he might want to inform her about the haunting:

+ TellTopic @darkTower
  "<q>You know, the folks in Doomsville even think their local tower is
   haunted,</q> you remark. <.inform tower-haunted>\b
   Mavis shakes her head tut-tutting away to herself. <q>There's no
   end to superstition and folly,</q> she remarks. <q>Mind you, I heard
   a similar story about Spookhampton.</q> <.reveal spookhampton-haunted> "
   
  isActive = gRevealed('tower-haunted') && !gInformed('tower-haunted')
;

Note how we use the gInformed() macro in the isActive property so that the player character won't tell Mavis the same piece of information twice (we might also use it elsewhere). Note how we also use the gRevealed() macro to ensure that the player character can't tell Mavis about the haunting until he knows about it himself. Finally, note that we use the <.reveal spookhampton-haunted> tag to note that the player character has just learned another snippet of information about alleged hauntings.

There's one further feature of the <.reveal> and <.inform> tags to bear in mind. The adv3Lite library assumes that if other actors are in earshot when a conversation is going on between the player character and an NPC, the other actors will be listening in on the conversation and hence will learn any piece of information that's imparted with a <.reveal> or <.inform> tag. This in fact extends to the NPC the player character is conversing with as well (though not to the player character, whose knowledge is accessed entirely though what's been revealed). So, for example, if Sally were listening in on the player character's conversation with Bob, then a <.reveal tower-haunted> or a <.inform tower-haunted> tag output in the course of that conversation would add the 'tower-haunted' key to both Bob's and Sally's table of inform keys, so that thereafter both bob.informedAbout('tower-haunted') and sally.informedAbout('tower-haunted') would return true. This seems more realistic than supposing that Sally would remain ignorant of the tower haunting rumours when Bob was discussing them with the player character right in front of her. It also ensures that Bob is reckoned as being informed about anything he has revealed to the player character, which is again surely more realistic than the opposite assumption. Note, however, that the <.reveal> tag only causes other actors to be notified of what's just been revealed if it's used during the course of conversation; if it's used for some other purpose (e.g. to set a flag when an object is examined), the notifications won't take place (unless a conversation happens to be taking place at the same time). If you want to ensure that no notifications take place, use gReveal(tag) instead.

If you don't want this 'overhearing' behaviour, you can also control it through use of the informOverheard and actorInformOverheard properties. The imparting of information to other actors in earshot can be prevented by setting informOverheard = nil on the current interlocutor's current ActorState, or actorInformOverheard = nil on the Actor (when the current ActorState is nil). To prevent the overhearing behaviour globally, set informOverheard = nil on the actor or on the whole Actor class.

The fact that an information-key revealed to the player character is always the same information-key that informs one or more NPCs explains why such keys must be regarded as occupying a global namespace within any given game (even if that weren't a good idea in any case to avoid confusion).

Although an actor (or Consultable) is unlikely to forget a fact it's previously acquired, if you want that to happen you can call its forget(tag) method, where tag is the fact string-tag representing the fact you want it to forget.

Knowledge, Truth and Belief

So far we have talked about knowledge of facts as if any fact asserted by the Player Character or NPC were automatically true. If Bob asserts that "The capital of Spain is Lisbon<.reveal spain-lisbon>", then testing for gRevealed('spain-lisbon') only tells us that the player character has been told something about Spain and Lisbon. It doesn't tell us whether the putative fact that has been revealed to the player character is true, or whether the player character believes it (and likewise for anything the Player Character might inform other actors about).

For many if not most purposes, this may well be fine. But if you want to track the credibility of the information that's being exchanged, you can associate it with one of five values: true, likely, dubious, unlikely or untrue. The last four of these are enums defined in the library for the purpose.

To associate one of these values with a knowledge tag, you can call gRevealed() or whateveActor.setInformed() with a second optional parameter, e.g. gRevealed('spain-lisbon', untrue) or bob.setInformed'wet-tomorrow', likely). You can also pass this second optional argument in a <.reveal> or <.inform> tag provided you use an = sign directly between the tag name and the second argument, e.g. <.reveallisbon-spain=untrue> or <.inform wet-tomorrow=likely> (this is necessary because if you just used <.inform wet-tomorrow likely> the game would assume that 'wet-tomorrow likely' was the name of your knowledge tag and would store it in the informed name table with the default value of true).

Note that these optional values are intended to reflect the attitude of the relevant character in the game — player character or NPC — towards the item of putative knowledge associated with a fact-tag, rather than that of you, the game author.

Before version 1.7 the macro gRevealed(key) and gInformed(key) along with the associated method actor.informedAbout(key) just returned true or nil. Now they return the value associated with key, which might be nil if the information has never been supplied at all, but could otherwise be true or any other value that's been associated with key This is unlikely to affect older code if it simply tests for if(gRevealed(whatever)) or if(gInformed(whatever)), since any non-nil (and non-zero) value will meet such conditions, but if this a problem for backward compatibility in your game you can set libGlobal.revealedTrueOrFalseOnly to true to restore the previous behaviour.

Player Knowledge and Player Character Knowledge

Thus far we have treated Player Knowledge and Player Character knowledge as if they were same thing, insofar as gReveal()/gRevealed() effectively set and track both. If there is only ever one player character in the game, this is unlikely to matter. But if the player character ever changes, relying on gRevealed() to test what the new player character knows would cause the new player character to have inherited the old player character's knowledge, which may not be what you want. To get round this problem you can call knowsAbout(item) or informedAbout(item) on the various player characters instead of using gRevealed. You may wish to use the macro gKnown(item) for this purpose: this macro expands to (gPlayerChar.knowsAbout(obj)). Conversely you may wish to use the macro gSetKnown(item) to set the player character knowledge, if it's distinct from the player's knowledge. You should be able to use the tag <.known item> to do this with a fact tag provided item contains at least one character that can't occur in an an object name (in other words, anything other than an alphanumeric character or an underscore). You can force this to occur by prefacing the fact tag with $, which will then be stripped from the tag before it's passed to gActor.setKnowsAbout. If you'd rather use some other character or string for this purpose, you can set it on the factTagMarker property of the conversationManager object, but the string you choose should be a special character, not an alphanumeric one or underscore.

What goes on behind the scenes here is that the results of revealing are stored in libGlobal.revealedNameTab while the results of gInform/gSetKnown are stored in the player character's informedAboutTab.

Finally, if you'd prefer to use an alternative (and perhaps more logical) naming system for the tags and macros, you can use pcKnows(arg) and <.pcknow arg> to test and set the player character's knowledge of arg and npcKnows(arg) and <.npcknow arg> to test and set the current interlocutor's knowledge of arg in conversational contexts. In each case arg can be either the programmatic name of a game object (Thing or Topic) or a fact tag (preferably prededed by a $ in <.pcknow arg>) and npcKnows(arg)) to mark it out as string argument rather than an object name. So, for example, you'd use pcKnows('troubles') but <.pcknow $troubles>. We'll say more about this alternative scheme (and when to use it) below.

Summary and Recommendations

You are, of course, free to use whichever set of tag names and macro names you wish, but here we offer some suggestions on which might be more helpful on two different scenarios.

Scheme 1, which uses the backward-compatible naming scheme, may well be the better option in games where you're happy to largely conflate player and player character knowledge. These will normally be games in which the player character never changes and/or there is little need to track NPC knowledge of the existence of objects (Things and Topics). In which case, you could stick to employing the following tags and macros:

Scheme 2, which uses the alternative system, may well be the better option in games where the player character changes and/or there is a need to distinguish Player knowledge, Player Character knowledge, and NPC knowledge, including which game objects and Topics different actors know about (know the existenced of) and possibly also which they have ssen.

In the first four of the above arg can either be the name of a game object (Thing or Topic) or a fact tag. For pcKnows(arg) and npcKnows(arg), if arg is intended as a fact tag it should be given as a single-quoted string (e.g. pcKnows('troubles')), For <.pcknown arg> and <.npcknown arg>, if arg is a fact tag it should be preceded by a $, e.g., <.pcknown $troubles>. Even though adv3Lite library may often be able to work out that arg can't be an object name it will make your code both safer and more readable if you always precede your tag names with a $ in these two cases (this isn't be necessary with any other conversation tags such as <.reveal arg> and the rest, but the library will strip any $ from the front of these too, just in case you include them by mistake).

Note that on this second scheme, you will need to set the knownProp, and possibly (but not necessarily) the seenProp, to different values, (e.g. &bobKnows, &sallyKnows) on every actor whose knowledge of game objects you need to track separately, and you will also need to manage these separate properties on all relevant Things, Topics, Rooms and Regions (where familiarity may vary between different actors). It will be even more work if you need to keep track of what different actors have seen.

To sever the link between Player knowledge and Player Character knowledge you may also wish to set libGlobal.informOnReveal to nil. This will prevent <.reveal> and gRevealed() from updating the informedNameTab of any actors, including the Player Character.

On Either Scheme, for testing and setting NPC knowledge outside conversational contexts where the NPC is the current interlocutor, call the methods knowsAbout(arg) and setKnowsAbout(arg) on the NPC in question, where arg can once again be am object name or a string tag.

In all probability, most games will be able to use Scheme 1 without players noticing anything amiss. A few games in which the finer details of who knows what is vital to the story-line or puzzle structure may need to use Scheme 2.