Preventing handling multiple things

In Inform 7, I need to block the player from taking (or otherwise manipulating) more than one thing during a single turn. A “Rule for deciding whether all includes something: it does not” takes care of TAKE ALL and TAKE EVERYTHING, but the player can still type TAKE FOO AND BAR or TAKE FOO, BAR. What’s the best way to completely disallow handling multiple things?

There’s probs a better way to do this, but the way that springs to mind is to run your commands through an command processor like so:

[code]The understood command is an indexed text which varies.

After reading a command:
if the player’s command includes “and”, say “One thing at a time please.” instead;
now the understood command is the player’s command;
if the understood command matches the regular expression “.|,”:
say “One thing at a time please.” instead.
[/code]

The problem with detecting commas is that it also catches commands to NPCs. I don’t have commandable NPCs in this case but it won’t work as a general solution.

I was really hoping for a way to disable the feature completely, but I suppose it’s hardcoded too deep in parser to just rip off.

The solution as presented is far too broad as you say, but of course extra conditions could be thrown in, like:

After reading a command: if the player's command includes "and", say "One thing at a time please." instead; now the understood command is the player's command; if the understood command matches the regular expression "\.|\," and the player's command does not include "bob/sally": say "One thing at a time please." instead.

Of course, a solution which handles punctuation less crudely would be more desirable.

You might be able to use the I7 phrase “if the I6 parser is running multiple actions”, to some good effect.

Or you could hack the GenerateMultipleActions routine (though I can’t tell if that’s a good idea).

Include (- [ GenerateMultipleActions initial_location k item; etype=MULTI_PE; L__M(##Miscellany, 33); ]; -) instead of "Generate Multiple Actions" in "OrderOfPlay.i6t".

Does this work?

After reading a command when the multiple object list is not empty: say "One at a time!"; reject the player's command.

A good way, though not the easy way, is to undeclare all the grammar entries that refer to multiple objects (take, get, pick up, remove, put, insert, drop) (and synonyms). Then redeclare them with [thing] instead of [things].

If you just wanted to stop commands like “take the apple and the beer and the backpack”, “take all” and give the message “You can’t use multiple objects with that verb.” instead, then this should work.

[spoiler][code]“Test”

Include (-

! This is an actual specified object, and is therefore where a typing error
! is most likely to occur, so we set:

oops_from = wn;

! So, two cases.  Case 1: token not equal to "held" (so, no implicit takes)
! but we may well be dealing with multiple objects

! In either case below we use NounDomain, giving it the token number as
! context, and two places to look: among the actor's possessions, and in the
! present location.  (Note that the order depends on which is likeliest.)

if (token ~= HELD_TOKEN) {
	i = multiple_object-->0;
	#Ifdef DEBUG;
	if (parser_trace >= 3) print "  [Calling NounDomain on location and actor]^";
	#Endif; ! DEBUG
	l = NounDomain(actors_location, actor, token);
	if (l == REPARSE_CODE) return l;                  ! Reparse after Q&A
	if (indef_wanted == INDEF_ALL_WANTED && l == 0 && number_matched == 0)
		l = 1;  ! ReviseMulti if TAKE ALL FROM empty container

	if (token_allows_multiple && ~~multiflag) {
		if (best_etype==MULTI_PE) best_etype=STUCK_PE;
		multiflag = true;
	}
	if (l == 0) {
		if (indef_possambig) {
			ResetDescriptors();
			wn = desc_wn;
			jump TryAgain2;
		}
		!if (etype == MULTI_PE or TOOFEW_PE && multiflag) etype = STUCK_PE;
		etype=CantSee();
		jump FailToken;
	} ! Choose best error

	#Ifdef DEBUG;
	if (parser_trace >= 3) {
		if (l > 1) print "  [ND returned ", (the) l, "]^";
		else {
			print "  [ND appended to the multiple object list:^";
			k = multiple_object-->0;
			for (j=i+1 : j<=k : j++)
				print "  Entry ", j, ": ", (The) multiple_object-->j,
					  " (", multiple_object-->j, ")^";
			print "  List now has size ", k, "]^";
		}
	}
	#Endif; ! DEBUG

	if (l == 1) {
		if (~~many_flag) many_flag = true;
		else {                                ! Merge with earlier ones
			k = multiple_object-->0;            ! (with either parity)
			multiple_object-->0 = i;
			for (j=i+1 : j<=k : j++) {
				if (and_parity) MultiAdd(multiple_object-->j);
				else            MultiSub(multiple_object-->j);
			}
			#Ifdef DEBUG;
			if (parser_trace >= 3)
				print "  [Merging ", k-i, " new objects to the ", i, " old ones]^";
			#Endif; ! DEBUG
		}
	}
	else {
		! A single object was indeed found

		if (match_length == 0 && indef_possambig) {
			! So the answer had to be inferred from no textual data,
			! and we know that there was an ambiguity in the descriptor
			! stage (such as a word which could be a pronoun being
			! parsed as an article or possessive).  It's worth having
			! another go.

			ResetDescriptors();
			wn = desc_wn;
			jump TryAgain2;
		}

		if ((token == CREATURE_TOKEN) && (CreatureTest(l) == 0)) {
			etype = ANIMA_PE;
			jump FailToken;
		} !  Animation is required

		if (~~many_flag) single_object = l;
		else {
			if (and_parity) MultiAdd(l); else MultiSub(l);
			#Ifdef DEBUG;
			if (parser_trace >= 3) print "  [Combining ", (the) l, " with list]^";
			#Endif; ! DEBUG
		}
	}
}

else {

! Case 2: token is "held" (which fortunately can't take multiple objects)
! and may generate an implicit take

	l = NounDomain(actor,actors_location,token);       ! Same as above...
	if (l == REPARSE_CODE) return l;
	if (l == 0) {
		if (indef_possambig) {
			ResetDescriptors();
			wn = desc_wn;
			jump TryAgain2;
		}
		etype = CantSee(); jump FailToken;            ! Choose best error
	}

	! ...until it produces something not held by the actor.  Then an implicit
	! take must be tried.  If this is already happening anyway, things are too
	! confused and we have to give up (but saving the oops marker so as to get
	! it on the right word afterwards).
	! The point of this last rule is that a sequence like
	!
	!     > read newspaper
	!     (taking the newspaper first)
	!     The dwarf unexpectedly prevents you from taking the newspaper!
	!
	! should not be allowed to go into an infinite repeat - read becomes
	! take then read, but take has no effect, so read becomes take then read...
	! Anyway for now all we do is record the number of the object to take.

	o = parent(l);
	if (o ~= actor) {
		#Ifdef DEBUG;
		if (parser_trace >= 3) print "  [Allowing object ", (the) l, " for now]^";
		#Endif; ! DEBUG
	}
	single_object = l;
} ! end of if (token ~= HELD_TOKEN) else

! The following moves the word marker to just past the named object...

wn = oops_from + match_length;

-) instead of “Parse Token Letter D” in “Parser.i6t”.

Include (-

! Object(s) specified now: is that the end of the list, or have we reached
! "and", "but" and so on?  If so, create a multiple-object list if we
! haven't already (and are allowed to).

.NextInList;

o = NextWord();

if (o == AND1__WD or AND2__WD or AND3__WD or BUT1__WD or BUT2__WD or BUT3__WD or comma_word) {

	#Ifdef DEBUG;
	if (parser_trace >= 3) print "  [Read connective '", (address) o, "']^";
	#Endif; ! DEBUG

	!if (~~token_allows_multiple) {
		!if (multiflag) jump PassToken; ! give UPTO_PE error
		etype=MULTI_PE;
		jump FailToken;
	!}

	if (o == BUT1__WD or BUT2__WD or BUT3__WD) and_parity = 1-and_parity;

	if (~~many_flag) {
		multiple_object-->0 = 1;
		multiple_object-->1 = single_object;
		many_flag = true;
		#Ifdef DEBUG;
		if (parser_trace >= 3) print "  [Making new list from ", (the) single_object, "]^";
		#Endif; ! DEBUG
	}
	dont_infer = true; inferfrom=0;           ! Don't print (inferences)
	jump ObjectList;                          ! And back around
}

wn--;   ! Word marker back to first not-understood word

-) instead of “Parse Token Letter E” in “Parser.i6t”.

Include (-

[ Adjudicate context i j k good_ones last n ultimate flag offset;
#Ifdef DEBUG;
if (parser_trace >= 4) {
print " [Adjudicating match list of size ", number_matched,
" in context ", context, “^”;
print " ";
if (indef_mode) {
print "indefinite type: ";
if (indef_type & OTHER_BIT) print "other ";
if (indef_type & MY_BIT) print "my ";
if (indef_type & THAT_BIT) print "that ";
if (indef_type & PLURAL_BIT) print "plural ";
if (indef_type & LIT_BIT) print "lit ";
if (indef_type & UNLIT_BIT) print "unlit ";
if (indef_owner ~= 0) print “owner:”, (name) indef_owner;
new_line;
print " number wanted: ";
if (indef_wanted == INDEF_ALL_WANTED) print “all”; else print indef_wanted;
new_line;
print " most likely GNAs of names: ", indef_cases, “^”;
}
else print “definite object^”;
}
#Endif; ! DEBUG

j = number_matched-1; good_ones = 0; last = match_list-->0;
for (i=0 : i<=j : i++) {
	n = match_list-->i;
	match_scores-->i = good_ones;
	ultimate = ScopeCeiling(n);

	if (context==HELD_TOKEN && parent(n)==actor)
	{   good_ones++; last=n; }
	if (context==MULTI_TOKEN && ultimate==ScopeCeiling(actor)
		&& n~=actor && n hasnt concealed && n hasnt scenery) 
	{   good_ones++; last=n; }
	if (context==MULTIHELD_TOKEN && parent(n)==actor)
	{   good_ones++; last=n; }

	if (context==MULTIEXCEPT_TOKEN or MULTIINSIDE_TOKEN)
	{   if (advance_warning==-1)
		{   if (context==MULTIEXCEPT_TOKEN)
			{   good_ones++; last=n;
			 }
			if (context==MULTIINSIDE_TOKEN)
			{   if (parent(n)~=actor) { good_ones++; last=n; }
			 }
		}
		else
		{   if (context==MULTIEXCEPT_TOKEN && n~=advance_warning)
			{   good_ones++; last=n; }
			if (context==MULTIINSIDE_TOKEN && n in advance_warning)
			{   good_ones++; last=n; }
		}
	 }
	if (context==CREATURE_TOKEN && CreatureTest(n)==1)
	{   good_ones++; last=n; }
	
	match_scores-->i = 1000*(good_ones - match_scores-->i);
}
if (good_ones == 1) return last;

! If there is ambiguity about what was typed, but it definitely wasn't
! animate as required, then return anything; higher up in the parser
! a suitable error will be given.  (This prevents a question being asked.)

if (context == CREATURE_TOKEN && good_ones == 0) return match_list-->0;

if (indef_mode == 0) indef_type=0;

ScoreMatchL(context);
if (number_matched == 0) return -1;

if (indef_mode == 0) {
	!  Is there now a single highest-scoring object?
	i = SingleBestGuess();
	if (i >= 0) {

		#Ifdef DEBUG;
		if (parser_trace >= 4) print "   Single best-scoring object returned.]^";
		#Endif; ! DEBUG
		return i;
	}
}

if (indef_mode == 1 && indef_type & PLURAL_BIT ~= 0) {
	!if (context ~= MULTI_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN
				 !or MULTIINSIDE_TOKEN) {
		etype = MULTI_PE;
		return -1;
	!}
	i = 0; offset = multiple_object-->0;
	for (j=BestGuess(): j~=-1 && i<indef_wanted && i+offset<MATCH_LIST_WORDS-1:
		j=BestGuess()) {
		flag = 0;
		BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
		if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, j)) == 0) {

			if (j hasnt concealed && j hasnt worn) flag = 1;
		
			if (context == MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN && parent(j) ~= actor)
				flag = 0;

			if (action_to_be == ##Take or ##Remove && parent(j) == actor)
				flag = 0;

			k = ChooseObjects(j, flag);

			if (k == 1)
				flag = 1;
			else {
				if (k == 2) flag = 0;
			}
		} else {
			flag = 0; if (RulebookSucceeded()) flag = 1;
		}
		EndActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
		if (flag == 1) {
			i++; multiple_object-->(i+offset) = j;
			#Ifdef DEBUG;
			if (parser_trace >= 4) print "   Accepting it^";
			#Endif; ! DEBUG
		}
		else {
			i = i;
			#Ifdef DEBUG;
			if (parser_trace >= 4) print "   Rejecting it^";
			#Endif; ! DEBUG
		}
	}
	if (i < indef_wanted && indef_wanted < INDEF_ALL_WANTED) {
		etype = TOOFEW_PE; multi_wanted = indef_wanted;
		multi_had=i;
		return -1;
	}
	multiple_object-->0 = i+offset;
	multi_context = context;
	#Ifdef DEBUG;
	if (parser_trace >= 4)
		print "   Made multiple object of size ", i, "]^";
	#Endif; ! DEBUG
	return 1;
}

for (i=0 : i<number_matched : i++) match_classes-->i = 0;

n = 1;
for (i=0 : i<number_matched : i++)
	if (match_classes-->i == 0) {
		match_classes-->i = n++; flag = 0;
		for (j=i+1 : j<number_matched : j++)
			if (match_classes-->j == 0 && Identical(match_list-->i, match_list-->j) == 1) {
				flag=1;
				match_classes-->j = match_classes-->i;
			}
		if (flag == 1) match_classes-->i = 1-n;
	}
 n--; number_of_classes = n;

#Ifdef DEBUG;
if (parser_trace >= 4) {
	print "   Grouped into ", n, " possibilities by name:^";
	for (i=0 : i<number_matched : i++)
		if (match_classes-->i > 0)
			print "   ", (The) match_list-->i, " (", match_list-->i, ")  ---  group ",
			  match_classes-->i, "^";
}
#Endif; ! DEBUG

if (indef_mode == 0) {
	if (n > 1) {
		k = -1;
		for (i=0 : i<number_matched : i++) {
			if (match_scores-->i > k) {
				k = match_scores-->i;
				j = match_classes-->i; j = j*j;
				flag = 0;
			}
			else
				if (match_scores-->i == k) {
					if ((match_classes-->i) * (match_classes-->i) ~= j)
						flag = 1;
				}
		}

	if (flag) {
		#Ifdef DEBUG;
		if (parser_trace >= 4) print "   Unable to choose best group, so ask player.]^";
		#Endif; ! DEBUG
		return 0;
	}
	#Ifdef DEBUG;
	if (parser_trace >= 4) print "   Best choices are all from the same group.^";
	#Endif; ! DEBUG
	}
}

!  When the player is really vague, or there's a single collection of
!  indistinguishable objects to choose from, choose the one the player
!  most recently acquired, or if the player has none of them, then
!  the one most recently put where it is.

if (n == 1) dont_infer = true;
return BestGuess();

]; ! Adjudicate

-) instead of “Adjudicate” in “Parser.i6t”.

The Testing Room is A Room.

An apple, a beer and a backpack are in the testing room.

Test me with “eat apple / drink beer / take backpack / take apple / drop apple / take all / take the apple and the beer and the backpack”.[/code][/spoiler]

This will prevent taking multiple things at once.

Hope this helps.