A very simple TADS3/adv3 module providing an output toggle

This is super simple, but I’ve ended up re-writing nearly identical snippets of code several times, so here’s a module: outputToggle github repo.

Usage is simple:

           gOutputOff;

…to disable all normal output, and…

           gOutputOn;

…to re-enable it.

About 90% of this is just a simple OutputFilter, but in some circumstances you also have to twiddle the transcript. The module’s only dozen lines of code but a) automagically adds itself to mainOutputStream, b) takes care of the the transcript (if there is a transcript), and c) provides a macro for using it.

4 Likes

Hm, interesting. You are using filterText. Here is how I implemented that functionality:

modify mainOutputStream
    outputOn = true
    enable() { outputOn = true; }
    disable() {outputOn = nil; }
    writeFromStream(txt) {
        /* if an input event was interrupted, cancel the event */
        inputManager.inputEventEnd();

        /* write the text to the console - JJMcC modified below */
        if (outputOn) aioSay(txt);
    }
;

Is there a benefit to the filterText approach over mine? Other than the elegance of an add-on rather than a modify, obviously :slight_smile:

2 Likes

FTR I mainly find this useful when replaying command logs to get to a debug point.

1 Like

How do you deal with a situation where you want to suppress output “within” an action?

For example, one of the things I’m working on is providing a “third person” version of stock adv3 actions that only really work if the actor taking the action. >EXAMINE, for example, which by default displays the examined object’s description. Having an object description show up in the transcript because Alice decided to examine something doesn’t make a lot of sense.

Say we’ve defined our game world like this:

startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";
+alice: Person 'Alice' 'Alice'
        "She looks like the first person you'd turn to in a problem. "
        isHer = true
        isProperName = true
;
+flower: Thing 'smelly flower' 'flower'
        "A smelly flower. "
        smellPresence = true
        smellDesc = "It smells like a flower, only more so. "
;

The player, Alice, a pebble, and a flower in a single room. The flower has smell presence and a smell description. So we want to modify Thing.basicExamineSmell() to allow Alice to smell the flower. In stock adv3, doing a newActorAction(alice, Smell, flower); will work, but it will output “It smells like a flower, only more so.” the same as if the player had done >X FLOWER themselves, without any indication that the message is appearing because Alice did something.

Because examining things can have side effects, I want to be able to do the default examine action but suppress the output, and then output an informational message to indicate an NPC just took an action. So, using the mainOutputStream output toggle, something like:

modify Thing
        // THIS DOESN'T WORK
        basicExamineSmell(explicit) {
                // Check to see if the current actor is the player.  If
                // so, just do the adv3 default stuff and return.
                if(gActor == gPlayerChar) {
                        inherited(explicit);
                        return;
                }

                // We're not the player, so we're an NPC.  So we turn
                // off output, do whatever the default behavior is,
                // and then turn output back on.
                mainOutputStream.enabled = nil;
                inherited(explicit);
                mainOutputStream.enabled = true;

                // Finally, report that the NPC is doing something.
                defaultDescReport(&thirdPersonSmell, gActor, self);
        }
;

That is, we check to see if the current gActor is the player and if so just do the inherited behavior and return. Otherewise we (try to) turn off output, do the inherited behavior, turn output back on, and then output a stock report indicating that the actor took an action. Elsewhere I have:

modify playerActionMessages
        thirdPersonSmell(actor, obj) {
                return('{You/He} smell{s} <<obj.theName>>. ');
        }
;

…along with stock message for other actions like this.

Anyway, as alluded to in the comments above, this doesn’t work. We implement a system action to initiate some NPC actions:

DefineSystemAction(Foozle)
        execSystemAction() {
                aioSay('\n===START===\n ');
                newActorAction(alice, Smell, flower);
                newActorAction(alice, Smell, pebble);
                aioSay('\n===END===\n ');
        }
;
VerbRule(Foozle) 'foozle': FoozleAction VerbPhrase = 'foozle/foozling';

And this gives us:

>foozle
===START===
It smells like a flower, only more so.

Alice smells nothing out of the ordinary.  Alice smells the pebble.
===END===

Note that we have two separate failure modes here. The first is because the flower has an explicit smellDesc, which is a double-quoted string that gets output directly. This suppresses the “NPC action” message we added, which is a defaultDescReport. In the second case we get both the stock thingSmellDescMsg and our “NPC action” message, both as defaultDescReports.

If we re-write the system action to wrap everything inside our output toggle and remove the output toggles from defaultExamineSmell():

DefineSystemAction(Foozle)
        execSystemAction() {
                mainOutputStream.enabled = nil;
                aioSay('\n===START===\n ');
                newActorAction(alice, Smell, flower);
                newActorAction(alice, Smell, pebble);
                aioSay('\n===END===\n ');
                mainOutputStream.enabled = true;
        }
;
modify Thing
        basicExamineSmell(explicit) {
                if(gActor == gPlayerChar) {
                        inherited(explicit);
                        return;
                }

                inherited(explicit);

                defaultDescReport(&thirdPersonSmell, gActor, self);
        }
;

…but then we get no output (not even our NPC action message):

>foozle
===START===
===END===

That’s a specific example (that I’m still having problems with, beacuse the fact that descriptions can either be reports (which are output after the action) or double-quoted strings (which are output “immediately”) complicates any output toggling scheme. Complicated by the fact that new actions are always executed with their own transcripts.

But it’s a general class of problem I’m dealing with. Namely, wanting to execute an action “normally” (to, for example, be able to read e.g. the failure flag in reports, so just implementing a ComandTranscript class that silently discards reports won’t work)…while still suppressing all output.

2 Likes

Tangentially related to the above, there’s a new mechanism (and a couple macros to use it) in the outputFilter module.

In addition to using gOutputOff and gOutputOn (which directly set the state of the filtering mechanism), there is now a locking mechanism that works like:

     // Get a numbered lock, turning off output.
     f = gOutputLock;

     // Remove the numbered lock.  This will turn on output if this was
     // was the only lock.  If there are other locks, output will remain off.
     gOutputUnlock(f);

This is intended to make it easier to use in situations where there are multiple bits of code that might want to disable the output stream.

2 Likes

Yow, asked and answered! I had to laugh, because your examples are pretty much EXACTLY what I would have tried at first blush for the problems you stated. Also true that I hadn’t even considered going as deep in the paint on NPC actions as you are…

If you get lost, send up a flare! We may not solve your problems, but we can come get you! :grin:

(Your second example is basically my exact usage model, so I detected no problems - was working as desired!)

1 Like