Blue Lacuna ">" looking and undo/ending compatibility

Hi. In my Inform 7 game, I want to allow the player to ‘look’ by entering no command, like in Blue Lacuna. I’ve done this by hacking the parser error rules, but this method has caused some problems with undo functionality and phrases like “end the story.”

Blue Lacuna’s implementation is, shall we say, unorthodox:

Table of custom library messages (continued) Message Id Message Text [...] LibMsg <empty line> "[unless location is a flash-room][lookfudge][as normal][bold type][location][roman type][line break][description of location][emptylineinit][the initial appearance of a random initapped thing enclosed by location][emptylineperson][initial appearance of a random other uncarried person enclosed by location][end unless]" [ Hack of the year award.]
Here’s what my own implementation looks like:

[code]This is the rest of the turn rule:
follow the scene changing rules;
follow the every turn rules;
abide by the timed events rule;
abide by the advance time rule;
abide by the update chronological records rule;
abide by the adjust light rule;
abide by the note object acquisitions rule;
abide by the notify score changes rule;
follow the scene changing rules.

[…]

Rule for printing a parser error when the latest parser error is the I beg your pardon error (this is the accept Blue Lacuna style looking rule):
follow the after reading a command rules;
try looking;
follow the rest of the turn rule.[/code]
This produces the expected outcome most of the time. However, there are two crucial scenarios where it doesn’t.

  1. If I “undo” after a turn in which I tried looking using this rule, the “undo” skips over that turn.

  2. If I end the story in a turn in which I tried looking using this rule, the shutdown rulebook doesn’t run until I enter another command.

I tried looking through the Standard Rules in case there was anything I missed, but it wasn’t very helpful. (There aren’t even any rules defined for the ‘reading a command’ activity.) My guess is that there’s some I6 machinery that triggers when the player enters an actual command, but not during a parser error.

In a search for alternative solutions, I’ve done some googling and found Andrew Plotkin’s Unified Glulx Inputs. It would probably be useful for writing a Blue Lacuna looking rule that’s less of a hack. Unfortunately, it breaks Emily Short’s Basic Screen Effects and Menus, which I’m also using, and which I don’t quite have the expertise to adapt.

In conclusion, I’m looking for a workaround for this problem, like an I6 hook I can drop into my “accept Blue Lacuna style looking rule” to hack “undo” and “end the story” into working. Any advice would be appreciated.

Maybe this will work?

[code]After reading a command:
if the number of characters in “[the player’s command]” is 0:
change the text of the player’s command to “look”.

Include
(-
[ Keyboard a_buffer a_table nw i w w2 x1 x2;
sline1 = score; sline2 = turns;

while (true) {
	! Save the start of the buffer, in case "oops" needs to restore it
	for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;

	! In case of an array entry corruption that shouldn't happen, but would be
	! disastrous if it did:
	#Ifdef TARGET_ZCODE;
	a_buffer->0 = INPUT_BUFFER_LEN;
	a_table->0 = 15;  ! Allow to split input into this many words
	#Endif; ! TARGET_

	! Print the prompt, and read in the words and dictionary addresses
	PrintPrompt();
	DrawStatusLine();
	KeyboardPrimitive(a_buffer, a_table);

	! Set nw to the number of words
	#Ifdef TARGET_ZCODE; nw = a_table->1; #Ifnot; nw = a_table-->0; #Endif;

	! If the line was blank, get a fresh line (commented this code out)
	!if (nw == 0) {
	!	@push etype; etype = BLANKLINE_PE;
	!	players_command = 100;
	!	BeginActivity(PRINTING_A_PARSER_ERROR_ACT);
	!	if (ForActivity(PRINTING_A_PARSER_ERROR_ACT) == false) {
	!		PARSER_ERROR_INTERNAL_RM('X', noun); new_line;
	!	}
	!	EndActivity(PRINTING_A_PARSER_ERROR_ACT);
	!	@pull etype;
	!	continue;
	!}

	! Unless the opening word was OOPS, return
	! Conveniently, a_table-->1 is the first word on both the Z-machine and Glulx

	w = a_table-->1;
	if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) {
		if (oops_from == 0) { PARSER_COMMAND_INTERNAL_RM('A'); new_line; continue; }
		if (nw == 1) { PARSER_COMMAND_INTERNAL_RM('B'); new_line; continue; }
		if (nw > 2) { PARSER_COMMAND_INTERNAL_RM('C'); new_line; continue; }
	
		! So now we know: there was a previous mistake, and the player has
		! attempted to correct a single word of it.
	
		for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
		#Ifdef TARGET_ZCODE;
		x1 = a_table->9;  ! Start of word following "oops"
		x2 = a_table->8;  ! Length of word following "oops"
		#Ifnot; ! TARGET_GLULX
		x1 = a_table-->6; ! Start of word following "oops"
		x2 = a_table-->5; ! Length of word following "oops"
		#Endif; ! TARGET_
	
		! Repair the buffer to the text that was in it before the "oops"
		! was typed:
		for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i;
		VM_Tokenise(a_buffer,a_table);
	
		! Work out the position in the buffer of the word to be corrected:
		#Ifdef TARGET_ZCODE;
		w = a_table->(4*oops_from + 1); ! Start of word to go
		w2 = a_table->(4*oops_from);    ! Length of word to go
		#Ifnot; ! TARGET_GLULX
		w = a_table-->(3*oops_from);      ! Start of word to go
		w2 = a_table-->(3*oops_from - 1); ! Length of word to go
		#Endif; ! TARGET_
	
		! Write spaces over the word to be corrected:
		for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';
	
		if (w2 < x2) {
			! If the replacement is longer than the original, move up...
			for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i-- )
				a_buffer->i = a_buffer->(i-x2+w2);
	
			! ...increasing buffer size accordingly.
			#Ifdef TARGET_ZCODE;
			a_buffer->1 = (a_buffer->1) + (x2-w2);
			#Ifnot; ! TARGET_GLULX
			a_buffer-->0 = (a_buffer-->0) + (x2-w2);
			#Endif; ! TARGET_
		}
	
		! Write the correction in:
		for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);
	
		VM_Tokenise(a_buffer, a_table);
		#Ifdef TARGET_ZCODE; nw = a_table->1; #Ifnot; nw = a_table-->0; #Endif;
	
		return nw;
	}

	! Undo handling

	if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
		Perform_Undo();
		continue;
	}
	i = VM_Save_Undo();
	#ifdef PREVENT_UNDO; undo_flag = 0; #endif;
	#ifndef PREVENT_UNDO; undo_flag = 2; #endif;
	if (i == -1) undo_flag = 0;
	if (i == 0) undo_flag = 1;
	if (i == 2) {
		VM_RestoreWindowColours();
		VM_Style(SUBHEADER_VMSTY);
		SL_Location(); print "^";
		! print (name) location, "^";
		VM_Style(NORMAL_VMSTY);
		IMMEDIATELY_UNDO_RM('E'); new_line;
		continue;
	}
	return nw;
}

];
-) instead of “Reading the Command” in “Parser.i6t”.[/code]

What I’ve done is I’ve commented out the part of “Reading the Command” that handles “if the line was blank, get a fresh line.” Then I use an “After reading a command” rule to handle the case where there’s a blank command, by changing the command to what you want. I haven’t checked it with undo and “end the story,” but it might be safer than trying to hand-roll the turn sequence. Though I daren’t ever say that messing with the parser template code is safe.

(BTW, when I was copying out the Keyboard code to replace it, I had to change one instance of “–)” to “-- )” to keep the Inform 7 compiler from thinking that I’d ended the I6 inclusion prematurely. See the first warning in §27.19 of Writing with Inform.)

That works perfectly, matt w. Thanks!

Matt, that code looks incredibly useful. Mind if I put that into an extension? (Would add an activity for “modifying a blank line”, which by default just calls “printing a parser error” and rejects the player’s command.)

You’re welcome! Glad it worked.

Also, now that I take a closer look at the code, there’s some “undo handling” code at the end of Keyboard, which would get skipped if we were using the normal code for handling blank lines. Which also makes me realize that UNDO ordinarily rolls back the previous command, not the previous turn–if I type “get rock/dklfsjlsfj/undo” it undoes the parser error and I still have the rock. I guess there’s reasons for that behavior, like if we have parser errors that actually do things.

On preview: Draconis, absolutely! There may be a more elegant way to check when the input is blank, I’m not sure.

Done. This gives an alternate solution to your problem:

Include Empty Command Handling by Daniel Stelzer.

Rule for repairing an empty command: change the text of the player's command to "look".

(This works slightly differently than Matt’s code: rather than removing the empty line detection entirely, the parser just lets the new activity step in first. If that activity fails to do anything it falls through to the default “I beg your pardon?”. But if it makes a change, parsing continues from there as if no error occurred. So you can even interpose “oops” or “undo” or period-separated commands.)

Cool!