The Q Object and ReachProblemBlocker (adv3Lite)

I’m sitting here staring at the page in the Library Manual called “Querying the World Model.” I cannot make heads nor tails of it. It describes something called the Q object, but does not tell me how or in what circumstances I would modify this object. It asserts that the Q object delegates its activity to a Special object, but does not tell me how to link my code to a Special object, nor how to define a Special object.

Color me baffled.

The reason this comes up is that I’ve put some food in a locked transparent container (a cooler in a snack bar). The default output looks like this:

This is obviously wrong, and needs to be customized, but I don’t understand how to alter it. This message is created by:

BMsg(cannot reach, '{I} {can\'t} reach {the b} through {the obj}. ');

This message is invoked by the ReachProblemBlocker class. But when and how is an instance of that class invoked? I have no idea.

I’m not at home, so I don’t have access to a machine with TADS 3 or adv3Lite on it right now, but in outline:

(1) There’s probably never a reason to modify the Q object in your own code (though you might want to call one or more of its methods).

(2) The Q object delegates to the highest-ranking Special object. The library defines a couple of Specials that are used by default, which are used for purposes such as determining whether A is visible, reachable, smellable or audible from B (where A and B are two objects that you or the library is interested in testing a sensory connection between).

(3) In the particular case you describe, the ReachProblem class is ultimately being invoked from the touchObj PreCondition via its verifyPreCondition() and checkPreCondition methods which call Q.reachProblemVerify(a, b) and Q.reachProblemCheck(a,b), which in turn call QDefaults.reachProblemVerify(a,b) and QDefaults.reachProblemCheck(a,b).

The function of these reachProblemXXX methods is to build a list of obstacles that would prevent a from reaching b (which might include distance or the presence of a physical obstacle such as a closed container). The items in this list are objects of the ReachProblem class (or one of its subclasses) which are used to contain information about the nature of the problem preventing a reaching b and which can be used to describe that problem.

[All this may be a little clearer if you look up the appropriate classes and methods in the Library Reference Manual]

More specifically, a new ReachProblemBlocker object would normally be created in QDefaults.reachProblemVerify(a,b) which would effectively be invoked from the verifyPreCondition method of the touchObj PreCondition.

Now, the easiest way to customize the message that ReachProblemBlocker displays is probably to use a CustomMessages object, something like:

CustomMessages messages = [Msg(cannot reach, '{I} {can\'t} reach {the b} while {the subj obj} {is} closed. ')] active = gDobj.isIn(largeCooler) ;

The point of defining the active property here is to try to ensure that this message is only used when the direct object of the command is inside the cooler, rather than for any and all occasions when one object is blocking access to another, but you may want to use your own condition (and you’ll presumably want to compose your own message).

CAVEAT - as I said I’m not in front of a machine with TADS on it right now, so I can’t test this.

Thanks, Eric. What I don’t understand is this: If all of the CustomMessages are gathered into one list during preinit, how exactly does one insure that the active property applies only to THIS message, not to any other cannot reach messages?

…beyond that, however, your suggested code isn’t working. I’m not sure why, but it won’t compile. I get a nil object reference on the line

active = gDobj.isIn(cooler)

This can’t be because the value of gDobj has not yet been established – that would be a run-time error, not a compilation error. I’ll investigate further.

Interesting question. The bit about “gathered into one list during preinit” comes from Mike Roberts’s comment in his Mercury code. So far as I can see what it means is not that the library builds a consolidated list of customized messages during preinit, but that it builds a list of CustomMessages objects so that it can iterate through them to find the highest priorty one that’s currently active.

That said, now that I’m able to test my suggested fix, I find that my active property (as defined in my previous post) causes a run-time error. Here’s a version that avoids that and puts the CustomMessages object in a test-bed game that illustrates the solution:

[code]
#charset “us-ascii”

#include <tads.h>
#include “advlite.h”

versionInfo: GameID
IFID = ‘445C38A3-AD1B-4729-957A-F584600DE5C1’
name = ‘test’
byline = ‘by Jerry Ford’
htmlByline = ‘by
Jerry Ford

version = ‘1’
authorEmail = ‘Jerry Ford jerry.o.ford@gmail.com
desc = ‘Testing three person conversation.’
htmlDesc = ‘Testing three person conversation.’

;

gameMain: GameMainDef
initialPlayerChar = me
paraBrksBtwnSubcontents = nil

;

me: Actor ‘me’ @snackBar
“The main man.<.p>”
isHim = true

person = 2

;

snackBar: Room ‘Snack Bar’
"Not much here, apart from a cooler in the corner and a fish tank by the
wall. "
;

  • cooler: OpenableContainer, Fixture ‘cooler’
    isTransparent = true
    ;

++ pizza: Food ‘pizza’
"It looks good. "
;

  • tank: OpenableContainer, Fixture ‘fish tank; long glass; lid’
    "A glass long tank with an openable lid. "
    isTransparent = true

;

++ fish: Thing ‘goldfish’
;

CustomMessages
messages = [Msg(cannot reach, '{I} {can't} reach {the b} while {the subj obj} {is} closed. ')]
active = gAction && gDobj && gDobj.isIn(cooler)
;[/code]

That said, I can see that this may not be the most intuitive way to handle it, so here’s an alternative scheme:

#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

versionInfo: GameID
    IFID = '445C38A3-AD1B-4729-957A-F584600DE5C1'
    name = 'test'
    byline = 'by Jerry Ford'
    htmlByline = 'by <a href="mailto:jerry.o.ford@gmail.com">
                  Jerry Ford</a>'
    version = '1'
    authorEmail = 'Jerry Ford <jerry.o.ford@gmail.com>'
    desc = 'Testing three person conversation.'
    htmlDesc = 'Testing three person conversation.'

;

gameMain: GameMainDef
    initialPlayerChar = me
    paraBrksBtwnSubcontents = nil
   
;

me: Actor 'me' @snackBar
    "The main man.<.p>"
    isHim = true
    
    person = 2
;

snackBar: Room 'Snack Bar'
    "Not much here, apart from a cooler in the corner and a fish tank by the
    wall. "
;

+ cooler: OpenableContainer, Fixture 'cooler'
    isTransparent = true
    reachBlockedMsg(target)
    {
        gMessageParams(target);
        return '{I} {can\'t} get at {the target} while the cooler is closed. ';
            
    }
;

++ pizza: Food 'pizza'
    "It looks good. "
;

+ tank: OpenableContainer, Fixture 'fish tank; long glass; lid'
    "A glass long tank with an openable lid. "
    isTransparent = true
;

++ fish: Thing 'goldfish'
;
  
modify Thing
    reachBlockedMsg(target)
    {
        local obj = self;
        gMessageParams(obj);
        return  BMsg(cannot reach, '{I} {can\'t} reach {the target} through
            {the obj}. ');
    }
;

modify ReachProblemBlocker    
    reachBlockedMsg()
    {        
        return obstructor_.reachBlockedMsg(target_);
    }
;

This may be better since it allows you to customise the message on the object that’s causing the blockage.

If you (and anyone else) thinks that’s preferable I’ll incorporate it into the library in the next update (along with any similar changes to analogous messages). Let me know what you think.

That looks like a better solution, yes. There could be many transparent containers in a game, each of which needs its own message.

However, the parameter {the target} is not working for me. It produces the output:

To get at the [target], you’ll need to open the large cooler.

I can replace it with {the dobj} – that works, as long as the player only tries to take one object at a time. The response to ‘take pizza and beer’ (both being in the cooler) produces two lines of output, which is perhaps less than ideal, but not too bad.

Adding gMessageParams(target); to the code seems to fix the output. (There are still two lines of output, but I can live with that.)

In case anyone’s interested, I’ve now implemented the changes discussed above in the version uploaded to GitHub; but please be aware that this is not an official release, just the latest development version (it’s not fully tested and the documentation isn’t fully up-to-date, particularly the indexes and the Library Reference Manual). The version on GitHub also fixes one or two bugs that have been reported here or via email.