T3: notifyRemove, exit, and group actions

I’m attempting to implement an Empty action (for containers). It works – but if an object can’t be removed from the container for some reason (such as notifyRemove on the container calling exit on some object), my report never appears on-screen. What’s worse, the order of objects in the container’s contents list may cause the action to stop before some objects are moved – again, because exit stops the process. Here’s my code:

action() { local dest = nil; local floorless = nil; local removed = []; local loc = me.getOutermostRoom(); // If there is no floor, then we're in a FloorlessRoom. If the FloorlessRoom // has no bottomRoom, we don't care ... we'll just empty the container into nil. if (!loc.roomFloor) { floorless = true; dest = loc.bottomRoom; } else dest = loc; foreach(local cur in contents) { cur.moveInto(dest); // There might be a reason why something doesn't get moved, so don't add it // to the list unless the moveInto was successful. if (cur.isIn(dest)) removed = removed + cur; } // Now we have a list of the emptied-out items. local lister = objectDefLister; local liststr = lister.makeSimpleList(removed); local len = removed.length(); local str = '{You/he} remove{s} ' + liststr + ' from ' + theName; local termstr = ''; if (!floorless) termstr = '. '; else { termstr = ', and '; if ((len > 1) || (removed[1].isPlural)) termstr = termstr + 'they plunge'; else termstr = termstr + 'it plunges'; termstr = termstr + ' out of sight. '; } mainReport(str + termstr); }
I observe that when I use the library’s ‘take all from X’ command, this problem doesn’t appear. On the other hand, I don’t much care for how the library reports the action, but that may (or may not) be a separate issue.

I can use nestedAction(Take, cur) followed by nestedAction(Drop, cur) to move the items in the container … but then I won’t get my nicely formatted list of the objects being emptied.

The real question, I guess, is this: Is there a way for notifyRemove to stop the current move, other than by calling exit?

exit is just macro shorthand for throw new ExitSignal(), so, although I haven’t tested it, it occurs to me that another way round your problem is to catch an ExitSignal() (using try… catch) in your own code so that it doesn’t exit the action. Something like:

foreach(local cur in contents) {
       try
       {
           cur.moveInto(dest);
       }
       catch (ExitSignal es)
       {
            continue;
        }
       
        // There might be a reason why something doesn't get moved, so don't add it
        // to the list unless the moveInto was successful.
        if (cur.isIn(dest))
            removed = removed + cur;
    }

This comes with the standard health warning: I’ve not tested it (beyond ensuring that it compiles) so you may need to tweak it to get it to do what you want, assuming it works at all! But hopefully it may help point you in the right direction.

– Eric

Thanks, Eric. That works nicely.

I can see that this project is going to teach me a bit more about listers, exceptions, and probably other things as well!

Eric beat me to it, but this should work.

action() {
	local removed = [];
	local invalid = [];
	local dest = gPlayerChar.getOutermostRoom();
	t3SetSay({x: reportAfter(x)});
	foreach(local cur in contents) {
		try {
			cur.moveInto(dest);
		}
		catch (ExitSignal es) {
			invalid = invalid + cur;
		}
		finally {
			if (cur.isDirectlyIn(dest))
				removed = removed + cur;
		}
	}
	t3SetSay(say);
	local liststr = objectDefLister.makeSimpleList(removed);
	local errstr = objectDefLister.makeSimpleList(invalid); 
	mainReport('{You/he} remove{s} ' + liststr + ' from ' + theName + '. ');
	if (errstr.length)
		mainReport('However, {you/he} {are} unable to move{s} ' + errstr + '. '); 
}

It uses a custom tadsSay function to redirect any output from moving the individual items to the end.

I suspect (not sure) that in many cases, notifyRemove on the container will already have printed something – depending on the author, via a double-quoted string. So reiterating the failure to move in the manner you’re showing would cause a redundant output.

I need to do more testing, but since this code is intended to be wrapped up in an extension, I don’t want the author to have to alter his/her normal code any more than is unavoidable.

Changing the t3SetSay() function causes any output that would normally be printed to be intercepted, so there’s not much redundancy. It produces output like this.

As opposed to:

The “however” clause is a bit over the top, but IMO it reads much better to print the failures at the end rather than the beginning.

This is deeper than I understand. I had never run into t3SetSay(val), but if you’re setting it to the say function, won’t it stay that way for the rest of the game … and in that case, will there be any side effects?

Or is it the say function already? If so, then you seem to be causing it to re-order the stuff it’s preparing to output, but I wouldn’t even know where to look to find an explanation of how that works. It’s probably in the System Manual somewhere…

t3SetSay is documented in the System Manual article on display functions.

The first t3SetSay line is the custom function. It won’t affect any messages that already use the transcript, but it will catch the output from double-quoted strings and add them to the transcript using reportAfter. AFAIK there’s no other way to intercept double-quoted strings, so it’s a handy trick to have.

The second t3SetSay line changes the behavior back to normal. ‘say’ is the default adv3 display function; not sure if that’s documented somewhere. I had to search the source code to find it.

So the transcript redirect is only in effect during the foreach loop, when objects might be printing stuff to the screen in an uncontrolled way.