Help examining possessions in backdrops [SOLVED]

I would like to allow the player to examine the backdrop “The Captain’s Suit” while they are in one of the locker rooms in my game (also “The Ensign’s Suit”, “The Doctor’s Suit”, and so on).

I know that I can include more Understand text to do what I want, but this gets quite long-winded and creates issues if I decide to add characters later on.

I’ve found how to fix the apostrophe in the player’s command (thank you Zed) and have included that in my code.

The Suit Rooms is a region. The Aft Locker Room and the Fore Locker Room are in the Suit Rooms.

The zero g suits are a backdrop in the Suit Rooms. Understand "[somebody] suit", "[somebody]'s suit", "suit", "spacesuit" as zero g suits. The description is "The suits are attached to the walls."

After reading a command:
	let x be "[the player's command]";
	replace the text "'s" in x with " [']s";
	change the text of the player’s command to x.

With this code even x suit causes a P12 error. (x captain's suit or captain suit does as well.):

With RULES on:

>x suit
[Rule "After reading a command" applies.]
[Rule "After reading a command" applies.]

*** Run-time problem P12: Too many activities are going on at once.

Commenting out the Understand portions with [somebody] removes the error.

Is the long-winded way the right way, is there a different better way, or is there something I’m missing here?

Thanks,
Thomas

1 Like

If you change ‘somebody’ to ‘any person’ it runs well for me.

Also, you didn’t seem to have this problem, but I had to put a hyphen between zero and g because my compiler thought I wanted the number ‘zero’ of g-suits, or something weird.

2 Likes

This one is interesting. The issue seems to be a big circular loop through various scope and parsing routines triggered by the use of “[somebody]” in the grammar line. This creates a line in the parse_name() routine:

w = ParseTokenStopped(ELEMENTARY_TT, CREATURE_TOKEN);

When trying to parse a command and deciding the scope:

  1. After processing compass directions, SearchScope() case (c.3) attempts to add objects in the room to scope by calling ScopeWithin(location, yourself).
  2. ScopeWithin() begins to loop through unconcealed objects in the location, submitting each to DoScopeActionAndRecurse().
  3. When the backdrop is reached via a call of DoScopeActionAndRecurse(backdrop, yourself), the routine immediately calls DoScopeAction(backdrop).
  4. Because the scope_reason is PARSING_REASON, DoScopeAction() calls MatchTextAgainstItem(backdrop).
  5. MatchTextAgainstItem() calls TryGivenObject(backdrop, nothing).
  6. TryGivenObject() notes that the backdrop has a parse_name() routine and calls it.
  7. During pass 2 of backdrop.parse_name(), the call to ParseTokenStopped() mentioned above is made.
  8. The token-parsing machinery invokes NounDomain() to see what nouns are available to match.
  9. NounDomain() makes a call to SearchScope(location, yourself), and it’s back to step 1.

The above is for 6M62. Does the issue persist in 10.1?

4 Likes

Without testing, I’m sure it does. The mechanisms you describe works the same way in all versions of I7. Invoking the parser from inside a parse_name routine (an “Understand ‘x’ as obj” line) is inherently recursive.

This is one of those situations where your first, simple idea turns out to be the easiest path:

I know that I can include more Understand text to do what I want, but this gets quite long-winded and creates issues if I decide to add characters later on.

Inform is long-winded and you might not add characters later on. If you do, remember about the space suits. :)

2 Likes

Did your researches explain why [any person] doesn’t trigger the same recursive loop?

I’m guessing because “[somebody]” requires a scope search, whereas “[any person]” uses a (mechanically simpler) iteration over all people in the game.

(Have not verified this, but pretty sure.)

1 Like

Duh. Of course! I’m an idiot.

I don’t think that “[any person]” is necessary, just “[person]” seems sufficient. The line in the generated parse_name() routine is restructured as:

w = ParseTokenStopped(ROUTINE_FILTER_TT, Noun_Filter_5);

with Noun_Filter_5() defined as:

[ Noun_Filter_5 
	x ! saved value of noun
	;
	x=noun;
	return ((noun ofclass K8_person));
];

The routine DoScopeAction() doesn’t seem to do anything when the context is ROUTINE_FILTER_TT, so the cycle is broken.

If using “[any person]” then the corresponding line in parse_name() becomes:

w = ParseTokenStopped(SCOPE_TT, Scope_Filter_5);

with Scope_Filter_5() defined as:

[ Scope_Filter_5 
	obj ! object loop variable
	o2 ! saved value of noun
	;
	switch (scope_stage) {
	    1: rfalse;
	    2: obj=noun;
	    objectloop(noun ofclass Object && (((noun ofclass K8_person) && (noun ofclass K8_person)))) {
	        o2 = noun; noun = obj;
	        suppress_scope_loops = true; PlaceInScope(o2, true); suppress_scope_loops = false;
	        noun = o2;
	    }
	    noun=obj;
	    3: nextbest_etype = NOTINCONTEXT_PE; return -1;
	}
];

Although the call to PlaceInScope() in turn calls DoScopeAction(), in this case it seems like DoScopeAction() wouldn’t invoke MatchTextAgainstObject() because the context is SCOPE_TT, breaking the cycle.

So, if you want to override scope, you can use “[any person]” in your grammar lines, and if you want typical scope rules to apply then you can use “[person]”. I think that “[somebody]” might offer some advantage for “talkable” things (if you are tinkering with that) because of the way CREATURE_TOKEN is handled at certain points, something will have to be done to stop the infinite recursion.

As far as what can be done about the root issue… I’m not sure. Every call to NounDomain() (and there are several sprinkled throughout the parser) can result in the recursion loop due to that routine’s call of SearchScope(). Some possibilities that have occurred to me:

  • Change the compiler’s construction of .parse_name() routines to use w = ParseTokenStopped(ROUTINE_FILTER_TT, Noun_Filter_N); where the Noun_Filter_N() routine is based on CreatureTest(). Given the comparison cases above, this seems like it might be the best option, but it will require I7 compiler modification.
  • Limit the allowable nesting for ParseToken() via a check of the existing parsetoken_nesting global. This doesn’t actually solve the root problem because it makes it impossible for the “[somebody]” token to match anything. It also probably imposes undesirable limits for those interested in extreme parsing stunts.
  • Add token context to DoScopeAction() so that it can skip past things that fail CreatureTest() when the context is CREATURE_TOKEN. This prevents the backdrop.parse_name() routine from being called by itself and solves the OP’s example issue, but it might not be enough on its own to prevent this type of issue in other contexts.

The 6M62 Include statements needed to implement the last option are:

Limited Fix
Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Parser.i6t: DoScopeActionAndRecurse (modified)
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ DoScopeActionAndRecurse domain nosearch context i ad n obj next_obj;

	DoScopeAction(domain, context); ! MODIFIED

 	! (a)
	if ((domain ~= nosearch) &&
	    ((domain ofclass K1_room or K8_person) || (IsSeeThrough(domain) == 1))) {
		obj = child(domain);
		while (obj) {
			next_obj = sibling(obj);
			if ((domain == actor) || (TestConcealment(domain, obj) == false)) {
				DoScopeActionAndRecurse(obj, nosearch, context);
			}
			obj = next_obj;
		}
	}

	! (b)
	if (domain provides component_child) {
		obj = domain.component_child;
		while (obj) {
			next_obj = obj.component_sibling;
			if ((domain == actor) || (TestConcealment(domain, obj) == false)) {
				DoScopeActionAndRecurse(obj, 0, context);
			}
			obj = next_obj;
		}
	}

	! (c)
	ad = domain.&add_to_scope;
	if (ad ~= 0) {
	    ! Test if the property value is not an object.
	    #Ifdef TARGET_ZCODE;
	    i = (UnsignedCompare(ad-->0, top_object) > 0);
	    #Ifnot; ! TARGET_GLULX
	    i = (((ad-->0)->0) ~= $70);
	    #Endif; ! TARGET_

	    if (i) {
	        ats_flag = 2+context;
	        RunRoutines(domain, add_to_scope);
	        ats_flag = 0;
	    }
	    else {
	        n = domain.#add_to_scope;
	        for (i=0 : (WORDSIZE*i)<n : i++)
	            if (ad-->i) {
	                DoScopeActionAndRecurse(ad-->i, 0, context);
			}
	    }
	}
];

-) instead of "DoScopeActionAndRecurse" in "Parser.i6t".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Parser.i6t: DoScopeAction (modified)
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ DoScopeAction item context; ! MODIFIED

	#Ifdef DEBUG;
	if (parser_trace >= 6)
	    print "[DSA on ", (the) item, " with reason = ", scope_reason,
	        " p1 = ", parser_one, " p2 = ", parser_two, "]^";
	#Endif; ! DEBUG

	@push parser_one; @push scope_reason;

	switch(scope_reason) {
		TESTSCOPE_REASON: if (item == parser_one) parser_two = 1;
		LOOPOVERSCOPE_REASON: if (parser_one ofclass Routine) indirect(parser_one, item);
		PARSING_REASON, TALKING_REASON:
		! BEGIN MODIFICATION
		if (context == CREATURE_TOKEN && CreatureTest(item) == false)  {
			! print "<DSA bailout!>^";
			return false;
		}
		! END MODIFICATION
		MatchTextAgainstObject(item, context); ! MODIFIED
	}

	@pull scope_reason; @pull parser_one;
];

-) instead of "DoScopeAction" in "Parser.i6t".

They need to be modified a bit for 10.1 because the syntax for inclusions has changed.

2 Likes