TADS3/adv3 convenience method for handling library messages via property ref?

I’ll lead with some demo code:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

modify playerActionMessages
        fidgetFoo(fidgeter, other) {
                gMessageParams(fidgeter, other);
                return('{You/he fidgeter} wait{s fidgeter} for {you/he other}
                        to do something.' );
        }
        fidgetBar(fidgeter, other) {
                gMessageParams(fidgeter, other);
                return('{You/he fidgeter} stare{s fidgeter} silently at
                        {you/he other}. ' );
        }
;

startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
+alice: Person 'Alice' 'Alice'
        "She looks like the first person you'd turn to in a problem. "
        isProperName = true
        isHer = true
;
++aliceAgenda: AgendaItem
        initiallyActive = true
        isReady = true
        invokeItem() {
                switch(rand(3)) {
                        case 1:
                                fidget(&fidgetFoo, me);
                                break;
                        case 2:
                                fidget(&fidgetBar, me);
                                break;
                        default:
                                fidget('Alice does nothing in particular. ');
                                break;
                }
        }
        fidget(msg, fidgetAt?) {
                local obj;

                obj = new MessageResult(msg, getActor, fidgetAt);
                defaultReport(obj.messageText_);
        }
;

versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;

This defines a couple new messages (as methods with arguments) on playerActionMessages. If you call them via defaultReport() or a related reporting macro, you could do something like:

   defaultReport(&fidgetFoo, alice, me);

This works because the reporting macros create a new report and add it to the transcript, and under the hood the report classes have constructors that evaluate the message/arguments by creating a MessageResult, and its constructor checks to see if the first arg to the constructor is a property, and if it is it checks playerActionMessages and does the right thing.

In the demo code as written I could absolutely just call defaultReport() (which is what eventually happens anyway), but in cases where I need the evaluated string value, what are the options?

Here I just create a MessageResult instance and then use the instance’s messageText_, which will contain the evaluated string. You could also just call playerActionMessages.fidgetFoo() and so on directly, but that’s substantially more awkward that just being able to use &fidgetFoo (or whatever) as an argument.

So…is there some more convenient way of doing this?

1 Like

Sorry… I don’t think I have a clear picture yet of what you have vs. what you hope to have…

Yeah, I think it’s one of those things where I think it’s a fairly straightforward thing…but describing it takes a page and a half.

Basically…say you want to “manually” output the generic “you can’t do that” message. That lives in playerActionMessages.cannotDoThatMsg. So you could do something like:

        local txt = playerActionMessages.cannotDoThatMsg;
        defaultReport(txt);

But that’s pretty verbose. But the library is nice enough to support
the much simpler usage:

        defaultReport(&cannotDoThatMessage);

That is, it’ll figure out that when you give defaultReport() a property, it’ll check playerActionMessages for the property and use it. It’ll actually do much more than that, in that it’ll also check to see if gActor has some other action message object defined, it’ll also juggle the object/direct objects associated with the current command so that message parameter substitutions associated with the message will be properly resolved, and so on.

The place where most of the “magic” happens is several layers deep (from the defaultReport() macro). Specifically, there’s a MessageResult class, and it’s constructor handles most of what I’ve just described.

So if you want to get the text that would be output by defaultReport(&cannotDoThatMessage) or defaultReport(&mustBeVisibleMsg, pebble) or whatever, you can do something like:

     local obj = new MessageResult(&mustBeVisibleMsg, pebble);

…and then obj.messageText_ will contain the resolved text…what would be output by defaultReport() if you passed the same arguments to it.

This works. And you can wrap it in a global function like:

resolveMsg(msg, [params]) {
        return(new MessageResult(msg, params...).messageText_);
}

…and then you can treat it like any other string-returning fuction. But my question is just…is there some library function or something that already does this/does it more cleanly? That is, something like one of the report functions, only it gives the resolved text as a return value instead of immediately adding it to the transcript.

Is there any drawback to your resolveMsg function? It looks pretty terse to me! I’m not aware of any hidden library facilities on that score.

As a non-programmer, I’m curious… is the return(expression); a C thing? All the TADS docs simply use return expression;.

2 Likes

No major ones that I can think of. Minor? Mainly that sometimes you might not need/want all of the side-effects of using MessageResult.resolveMessageText() (which is called by the MessageResult constructor)…like digging through the VM stack to set/change the object and DO for resolving the message.

Main reason I’m asking for alternative is a) it seems like something the library might already supply (and so I want to avoid re-inventing the wheel), and b) it involves grabbing a “private” property on the MessageResult instance, which is an indication that the library authors didn’t expect you to be poking around with it. T3 doesn’t actually enforce anything here, so the naming conventions (trailing underscore in the prop name) are purely advisory…but it’s something I still worry about, if for no reason other than not knowing anything about the object’s expected lifecycle (and so whether or not something else might step on the property unexpectedly).

As for using return(value) instead of return value, it’s something I do mostly because I find it easier to read. I also generally prefer if((foo == 1) && (bar == 2)) instead of if(foo == 1 && bar == 2) as well, for whatever that’s worth.

I think the ultraorthodox programmer position is that you shouldn’t use parens around return, because return is a reserved word/language construct and not a function (and there’s a school of thought that says those should be typographically distinct). If you care about that sort of thing.

Me, I almost always prefer formatting code in such a way that I’ll find it easiest to visually grep/parse/whatever when I’m coming back to it some time after initially writing it.

2 Likes

Mood. This is why I format braces, spaces, newlines the way I do. It’s not just neatness and visual ease, but it also contains information.

2 Likes

Yeah. When coding it’s important to keep in mind that the compiler doesn’t care at all about your whitespace/braces/variable naming conventions. That’s 100% for humans reading the code, and the biggest audience for any code you write is almost certainly you yourself.

2 Likes