Trying to Remove Object From Scope [Adv3Lite]

I have an object in a room named 'south;;s', which takes an Examine command.

The idea is if someone tries > LOOK SOUTH, then this object will get matched, and it will redirect the action to look through a window, which is described as “on the south wall”.

The problem is if the player tries to be silly, and enters > TAKE SOUTH, the game will say:

The south is fixed in place.

But I don’t want the game to even attempt to handle the action if this redirection object isn’t made for it. In short, I want the parser to say:

You see no south here.

For any action that is not Examine.

I tried to use the following:

class PeekTarget: Thing {
    // The object that will get the LOOK THROUGH action
    peekRemapTarget = nil

    isFixed = true

    // Basic redirections
    dobjFor(Search) { remap = peekRemapTarget }
    dobjFor(PeekInto) { remap = peekRemapTarget }
    dobjFor(PeekThrough) { remap = peekRemapTarget }
    dobjFor(LookThrough) { remap = peekRemapTarget }

    // For some reason, asDobjFor(LookThrough) did not work here,
    // so I had to fill out this command
    dobjFor(Examine) {
        verify() { }
        check() { }
        action() {
            doInstead(LookThrough, peekRemapTarget);
        }
        report() { }
    }

    // This is what is SUPPOSED to drop this object out of scope,
    // but it keeps crashing the game instead. I've included the error.
    filterResolveList(np, cmd, mode) {
        local actionMatches = nil;

        if (gActionIs(LookIn)) actionMatches = true;
        else if (gActionIs(Search)) actionMatches = true;
        else if (gActionIs(PeekInto)) actionMatches = true;
        else if (gActionIs(PeekThrough)) actionMatches = true;
        else if (gActionIs(LookThrough)) actionMatches = true;
        else if (gActionIs(Examine)) actionMatches = true;

        if (!actionMatches) {
            np.matches = np.matches.subset({m: m.obj != self});
        }
    }
}

The issue is attempting to do anything with this object now causes the follow error:

Stack trace report

Runtime error: nil object reference
–>/usr/local/share/frobtads/tads3/lib/adv3Lite/action.t, line 876
/usr/local/share/frobtads/tads3/lib/adv3Lite/parser.t, line 2305
/usr/local/share/frobtads/tads3/lib/adv3Lite/command.t, line 812
/usr/local/share/frobtads/tads3/lib/adv3Lite/command.t, line 880
/usr/local/share/frobtads/tads3/lib/adv3Lite/command.t, line 819
/usr/local/share/frobtads/tads3/lib/adv3Lite/parser.t, line 866
/usr/local/share/frobtads/tads3/lib/adv3Lite/parser.t, line 339
/usr/local/share/frobtads/tads3/lib/adv3Lite/main.t, line 180
/usr/local/share/frobtads/tads3/lib/adv3Lite/main.t, line 119
/usr/local/share/frobtads/tads3/lib/adv3Lite/misc.t, line 124
/usr/local/share/frobtads/tads3/lib/adv3Lite/main.t, line 70
/usr/local/share/frobtads/tads3/lib/adv3Lite/main.t, line 24
/usr/local/share/frobtads/tads3/lib/_main.t, line 217
/usr/local/share/frobtads/tads3/lib/_main.t, line 122
/usr/local/share/frobtads/tads3/lib/_main.t, line 31

The online docs don’t include line numbers for source files, so I copy-and-pasted the whole action.t file into a notepad, and scrolled. apparently line 876 has this:

872    /* apply verb-specific adjustments */
873    foreach (local i in lst)
874        scoreObject(cmd, role, lst, i);
875
876    /* apply object-specific adjustments */
877    foreach (local i in lst)
878        i.obj.scoreObject(cmd, role, lst, i);

Not really sure what to make of this.

I tried defining the Default action on the redirection object, but it doesn’t seem to do anything.

Defining a Doer did absolutely nothing. Because “south” wasn’t the name of an object in the room, the Doer refused to match at all.

Any help would be appreciated, thank you!

I could probably help in adv3 but…
If no one else gets this I’ll see what I can find in the Lite source…

Could you kludge objInScope() on Action, if Lite has such an equivalent?

1 Like

I don’t think that’s a thing in Adv3Lite… :slightly_frowning_face:

I did finally get a solution working, but it’s only possible through the interaction of several subsystems of my SpringThing game, so I’d need to ask @mathbrush how much code I could post before I would get disqualified, lol.

I’m making this game as fast as I can; working like an exhausted maniac for over a month straight, now. If I can find a moment when my body isn’t begging for sleep, then I’ll try and create a generalized solution and post it here.

Right now, though, I gotta pick up and keep sprinting forward. I’ll try to keep tabs on this, but the game is now past the point of creating a test scenario for any fixes. I also know very little about the differences between Adv3 and Adv3Lite.

Absolute worst-case-scenario, I’ll post the solution after the competition, as dumb as that sounds.

Again, posting the whole thing might wind up being a lot of code, or I’d need to design a brand new side-species of the relevant subsystems, and post it as a general extension.

1 Like

You can post code as long as player’s don’t get the full game experience from it. My guess is that you could post almost the entire thing and people would be surprised by the final product.

1 Like

I have a similar class in my personal library. It’s probably more complicated than you’re interested in (even I think it’s too complicated, but hey, it works).

The trick is not to make the south;;s class a Fixture, but a Decoration. Decoration is powerful because it holds a list of verbs it responds to. Everything else gets a stock response. (Unthing is likewise powerful because of this. You can, for example, add Unthing to a character’s inventory. It will never be listed by the Inventory command, but the player magically “knows” about it wherever they go. Good for things like memories, known spells, etc.)

I made mine subclass Distant, because that’s a better fit than Decoration for this application.

Here’s the full class. Like I said: complicated. But it works. (I use it everywhere in According to Cain.)

/**
 * A Distant object representing a location visible from the current location.
 *
 * The dir property should be set with a compass location object (northDir,
 * upDir, etc.)
 *
 * If dir and location are set, DistantLocation will provide a destination
 * that's the same location as the location's property of the same direction.
 *
 * If the location's direction property is anything other than an object
 * (string, code, etc.), DistantLocation will treat the destination as unreachable.
 *
 * Due to the way that DistantLocation sets its vocabulary ('east', 'north', etc.),
 * each location should only have one DistantLocation for a particular direction,
 * to avoid disambiguation problems.
 */
class DistantLocation: Distant, PreinitObject
  /** Compass direction of location (northDir, upDir, etc.) */
  dir = nil

  /** Message when attempting to reach a known distant location */
  cannotReachLocationMsg =
    BMsg(route unknown, '{I} {don\'t know} how to get there. ')

  destination {
    if (!location || !dir)
      return nil;

    // destination can only be a Room object
    if (location.propType(dir.dirProp) != TypeObject)
      return nil;

    local dest = location.(dir.dirProp);

    // TravelConnectors are not destinations unto themselves
    if (dest.ofKind(TravelConnector) && !dest.ofKind(Room))
      dest = dest.getDestination(gPlayerChar.getOutermostRoom);

    return dest;
  }

  decorationActions = (location && dir) ? [ Examine, GoTo ] : inherited

  dobjFor(Examine) {
    verify() {
      if (location && !gPlayerChar.isIn(getOutermostRoom))
        inaccessible(notImportantMsg);
      else
        inherited();
    }
  }

  // if the location's destination for the direction is set but it's not a
  // Room object, give a better explanation than "it's too far away."
  dobjFor(GoTo) {
    verify() {
      // use inflated rank to prefer this object's pathing to standard Distant
      // objects with destinations
      if (location && dir && gPlayerChar.isIn(getOutermostRoom))
        logicalRank(120);
      else
        inaccessible(cannotReachLocationMsg);
    }
    check() {
      local pt = (location && dir) ? location.propType(dir.dirProp) : TypeNil;
      if (pt == TypeNil)
        "<<notImportantMsg>>  ";
      else if (pt != TypeObject)
        sayDontKnowHowToReach();
    }
  }

  dobjFor(Default) {
    verify() {
      if (location && !gPlayerChar.isIn(getOutermostRoom))
        inaccessible(notImportantMsg);
      else
        inherited();
    }
  }

  iobjFor(Default) {
    verify() {
      if (location && !gPlayerChar.isIn(getOutermostRoom))
        inaccessible(notImportantMsg);
      else
        inherited();
    }
  }

  // Never include in ALL
  hideFromAll(action) {
    return true;
  }

  // PreinitObject
  execute() {
    if (!dir)
      return;

    local abbrev = dirAbbrevs[dir.name] ?? '';

    // add directional words for the direction
    addVocab(';;<<dir.name>> <<abbrev>>');
  }

  dirAbbrevs = static [
    'north' ->      'n',
    'northeast' ->  'ne',
    'east' ->       'e',
    'southeast' ->  'se',
    'south' ->      's',
    'southwest' ->  'sw',
    'west' ->       'w',
    'northwest' ->  'nw',
    'up' ->         'u',
    'down' ->       'd',
    'inside' ->     'in'
  ]
;

DistantLocation also adds the appropriate direction vocab if dir is set. GoTo commands also work, e.g., GO SOUTH doesn’t yak. dir has to point to a Room object; it can’t be a mere TravelConnector or double-quoted string. (It will work if the TravelConnector has a destination that’s a Room, though.)

Example:

+ DistantLocation 'a well'
  """
  A primitive well ringed by stones is to the west of here.
  """

  dir = westDir
;

Let me know if this works for you.

2 Likes

This is genius!!!

The solution I’ve found fits the rest of the codebase super smoothly, but I can probably make a test case in a fresh project once I’m not in full grind mode, and see how your code works in practice.

However: Just by reading over your code, and mentally processing logical cases, this looks like it would do exactly what I was looking for, though in a project that doesn’t already have custom looking mechanics already taking the wheel, lol. (Those custom mechanics might have also been what was causing issues before…)

I’m marking this as the solution, though, because this is exactly what would work in a general-use case.

Thank you for saving the day!

EDIT: Also, don’t mind me yoinking that into my toolbox as well. A future project will absolutely need your DistantLocation class. This is a problem I keep re-discovering, so having this stellar solution will save me a lot of grief in the future, lol.

Okay, I might post something later tonight or tomorrow, but I think our esteemed @jnelson has solved it, lol. My solution would be extremely clunky and bodgy in comparison, because it really only works through the other systems already in place. It’d be like me bringing a Rube Goldberg machine, while Jim just brought a simple wrench.

3 Likes