Purloining a Hidden Item (adv3Lite)

I have an important object that starts out isHidden … the player has to actually examine the place where it’s sitting in order for it to show up. But of course, a player may have played the game before, and may try to grab the thing without troubling to examine the place where it’s sitting. It would be rather confusing and annoying to have the parser claim there’s no such object in the room, when there is. Consequently, I’ve added an Unthing to the location, whose notImportantMsg is rather snarky but at least not misleading. ("Key? Is there a key around here somewhere? ")

The difficulty I’ve stumbled onto is this: In the course of testing, I will often need to purloin this object. (Okay, it’s a key.) And I can’t, because the parser prefers the more apparent Unthing rather than the isHidden key.

What I probably need to do is two things: First, when hidden things are purloined, they have to automatically become not hidden. Second, the purloin command has to ignore Unthings. I’m not sure how to hack the library so as to accomplish either of these things. I’ll give it a shot, but suggestions would be welcome.

BTW, this is the kind of information that would be dandy to add to the wiki, if anybody was using it. I’m not sure I remember how to log onto the wiki, though…

On further investigation, it appears the problem is not that the Unthing is preferred by the parser – if I comment it out, the Purloin action still doesn’t work – but that an item that isHidden is never considered by the Purloin action. The makeScopeUniversal() method of TAction doesn’t seem to have an exception for isHidden items – they should be added to scope. And the code for dobjFor(Purloin) doesn’t object to things that are hidden.

It appears that the offending method is Mentionable.matchNameCommon(). This begins, “if (isHidden) return 0;”. That would be a deal-killer, all right. The difficulty I now face is this: At the time when matchNameCommon is called, it appears that the action has not yet been resolved. gActionIs doesn’t know what’s going on. So if I attempt this in an #ifdef __DEBUG modify Mentionable block, it doesn’t work:

if(isHidden && !gActionIs(Purloin)) return 0;
I certainly don’t want to mess with matchNameCommon across the board! I want it to run as usual, except when the action is Purloin. But how can I do that when the action is not yet resolved?

Or am I misunderstanding the situation in some way? (That wouldn’t be unusual.)

The cheap answer, of course, is:

#ifndef __DEBUG isHidden = true #endif
The only issue with this approach is, I might want to send the game out for testing in debug mode (so as to allow my testers to be able to use purloin, gonear, and so forth). If I do that, then they will not encounter the actual hiddenness of the object in question. It will show up in their transcripts in an inappropriate manner, and any bugs that might result will remain uncaught.

So here’s the real answer: Add an extra debugging verb.

#ifdef __DEBUG DefineIAction(ShowHidden) execAction(cmd) { "Now revealing all hidden things. "; local obj = firstObj(Thing); do { if (obj.isHidden) obj.isHidden = nil; obj = nextObj(obj, Thing); } while (obj!= nil); } ; VerbRule(ShowHidden) 'showhidden' : VerbProduction action = ShowHidden verbPhrase = 'show/showing all hidden things' missingQ = 'what, me worry' ; #endif
If we wanted to hide them again during testing, it would be a tiny bit more complicated, because we’d have to add a wasHidden property to Thing to store the previous value of isHidden. Not too tricky, though. Something like this might be worth adding to the library … just a thought. Here’s a more complete version:

#ifdef __DEBUG DefineIAction(ShowHidden) execAction(cmd) { "Now revealing all hidden things. "; local obj = firstObj(Thing); do { if (obj.isHidden) { obj.isHidden = nil; obj.wasHidden = true; } obj = nextObj(obj, Thing); } while (obj!= nil); } ; VerbRule(ShowHidden) 'showhidden' : VerbProduction action = ShowHidden verbPhrase = 'show/showing all hidden things' missingQ = 'what, me worry' ; modify Thing wasHidden = nil ; DefineIAction(ReHide) execAction(cmd) { "Now rehiding all that was formerly hidden. "; local obj = firstObj(Thing); do { if (obj.wasHidden) { obj.isHidden = true; obj.wasHidden = nil; } obj = nextObj(obj, Thing); } while (obj!= nil); } ; VerbRule(ReHide) 'rehide' : VerbProduction action = ReHide verbPhrase = 'rehide/rehiding all formerly hidden things' missingQ = 'what, me worry' ; #endif
Of course, this is still a bit error-prone. If the tester unhides stuff, grabs something that is now unhidden, and then later needs to rehide the hidden things (perhaps to test something else), the ought-to-be-hidden thing that the tester grabbed will disappear (even if it’s in inventory), because it was never properly discovered in the normal course of play.

Ideally, then, a way to get purloin to directly access hidden things would still be better.

My initial reaction was that trying to fix this might risk introducing too many bugs elsewhere, but I think I’ve come up with a reasonably neat way to handle it. It would be a bit fiddly to describe the several patches needed to library code to get it to work, but you can download the fix from GitHub.

In essence the Action class now has an unhides property. If this is true, then the action will go ahead even if the object is hidden. In the new version unhides is true for the Purloin and GoNear actions, but game code could also set it to true for any custom debugging actions it cares to define. Using an action property here is rather neater than having ad hoc code relating to specific actions.

This new version also tweaks the handling of the Purloin action so that it sets isHidden to nil on the item purloined; otherwise you would have been able to purloin an item but still not interact with it once it had been purloined.

P.S. After today I’ll be away for a couple of weeks, so I may be a bit slow to respond to any further queries.