Novice here: removing "take all" for divergent storylines

I know that I’m being awfully ambitious with my first IF, but I wanted to author something for my students that will fit different demographics. In the intro, a shadowy figure presents the player with two objects: a dagger or a rose. The story will then change according to which object is picked (action or romance). My initial trouble (of a likely many) is undoing the native language of Inform7 that includes the “Take all” command. I’d rather the “Take all” command results in the man telling the player to choose only one object.

Any help appreciated. I look forward to being an active member on these boards.

Dealing with ALL and other multiple-object things is, in a word, fiddly; the following code draws heavily on the example The Left Hand of Autumn.

[code]Group-description-complete is a truth state that varies.

Before reading a command:
now group-description-complete is false.

Check taking:
if group-description-complete is true, stop the action;

Check taking:
if the number of entries in the multiple object list > 1 begin;
say “I’m sorry, you must choose a single object here.[paragraph break]”;
now group-description-complete is true;
stop the action;
end if;

The silently announce items from multiple object lists rule is listed instead of the announce items from multiple object lists rule in the action-processing rules.

This is the silently announce items from multiple object lists rule:
unless taking:
if the current item from the multiple object list is not nothing, say “[current item from the multiple object list]: [run paragraph on]”.

Definition: a thing is matched if it is listed in the multiple object list.
[/code]

Probably the simplest way is to redefine the verbs in question to apply only to one thing, rather than “things”:

[code]Understand the commands “get” and “pick” and “take” as something new.

Understand “take [something]” as taking.
Understand “get [something]” as taking.
Understand “pick up [something]” and “pick [something] up” as taking.
[/code]

Then it’s simply a matter of enabling the other actions associated with these verbs:

[code]Understand “take inventory” as taking inventory.
Understand “take off [something]” and “take [something] off” as taking off.
Understand “take [something] from [something]” and “take [something] off [something]” as removing it from.

Understand “get out/off/down/up” as exiting.
Understand “get in/on” and “get in/into/on/onto [something]” as entering.
Understand “get off/down [something]” as getting off.
Understand “get [something] from [something]” as removing it from.

There is room. A dagger and a rose are here.

Test me with “take all”.[/code]All of this understood syntax is taken straight from the Actions subtab of the Index tab.

It seems to me that disabling “take all” everywhere in the game may be a bit overpowered for what you want to do, which is to prevent the player from taking both the dagger and the rose. I think you can handle this a bit more finely using rules for deciding whether all include (section 17.34 in the manual) and adjusting the parser error message with rules for printing a parser error (section 17.33). This is what I came up with:

[code]Choice made is a truth state that varies. Choice made is false.

Rule for deciding whether all includes something when the player can touch the dagger and the player can touch the rose and choice made is false: it does not. [This disables “take all” in a room with the dagger and the rose when we haven’t chosen yet.]
Rule for printing a parser error when the latest parser error is the nothing to do error and the dagger is in the location and the rose is in the location: say “You must choose one of the dagger and the rose.”

Carry out taking the dagger: now choice made is true.
Carry out taking the rose: now choice made is true.

Starter is a room. A dagger and a rose are here.
North Room is north of starter. A hoojab, a whatsit, and a whatchamacallit are here.

Test me with “take all/n/take all/take all/drop all/s/take rose/take all”.
Test oops with “n/take all/s/drop all”.[/code]

The “nothing to do error” is the parser error that usually prints the message “There are none at all available!” As the first “test me” script shows, that will still behave normally when the player isn’t trying to take both the dagger and the rose.

As the “test oops” script shows, this will mess up when the player tries “drop all” when she should be choosing between the rose and the dagger. You may not need to worry about this in your game, but in any case it can be fixed with a bit of (very useful) I6 trickery. The “To decide what action-name is the action-to-be” line lets us figure out what action the player was trying to do, even when the command didn’t compile:

[spoiler][code]Choice made is a truth state that varies. Choice made is false.

To decide what action-name is the action-to-be: (- action_to_be -).

Rule for deciding whether all includes something when the player can touch the dagger and the player can touch the rose and choice made is false and the action-to-be is the taking action: it does not. [This disables “take all” in a room with the dagger and the rose when we haven’t chosen yet.]
Rule for printing a parser error when the latest parser error is the nothing to do error and the dagger is in the location and the rose is in the location and the action-to-be is the taking action: say “You must choose one of the dagger and the rose.”

Carry out taking the dagger: now choice made is true.
Carry out taking the rose: now choice made is true.

Starter is a room. A dagger and a rose are here.
North Room is north of starter. A hoojab, a whatsit, and a whatchamacallit are here.

Test me with “take all/n/take all/take all/drop all/s/take rose/take all”.
Test oops with “n/take all/s/drop all”.[/code][/spoiler]

(There may be a better way to fix that.)

Also, it sounds like the choice is dramatic enough that you might not want to let the player do anything other than choose the dagger or the rose when it’s presented. No trying to go north, no taking your inventory, no nothing. So you might have something like this:

Section 5.2 of the Inform Recipe Book has several examples of ways to do this.

Incidentally, if the shadowy figure is actually carrying the dagger and the rose, the normal taking action won’t work anyway unless you rewrite the “can’t take other people’s possessions” rule.

Instead of messing with the actions could you modify whatever mechanic triggers the scene change to not work if both objects are held?

“You’ve chosen both, which is not my intention. Please give one back to me now.”

Some good workarounds here. Thanks for all your code and help. Several work very well. (Perhaps I should have this story be open-sourced and have ya’ll write it for me :smiley: .)

Clearly, I need to delve deeper into the Inform documentation to understand how you arrive at these.

Cheers

Following is what I came up with. It builds a little functionality onto maga’s example in that it only tries to stop the player from taking more than one plot-critical thing – taking multiple non-critical things is OK, and taking more than one key item while taking multiple non-key items is also handled. Probably more than you are looking for, but it does use many interesting features you’ll probably want to get acquainted with and that I enjoyed the chance to learn more about (i.e. new properties, scenes, after rules, “was” conditions [see 9.13] for detecting intra-turn changes, every turn rules, action variables, lists).

Overall, though, I think maybe matt w’s suggestion of forcing the choice and only the choice is the right call. It is probably simpler to implement, and it really does highlight the significance of the decision to the player.

[code]Place is a room.

A thing can be plot-critical.

A cat is here. A hat is here. A bat is here. [to simulate likely presence of non-critical plot items in your scenario]

The stranger is a man in Place. “A stranger is here, offering you a choice between [a list of visible plot-critical things].” The stranger carries a rose and a dagger and a golden signet. The rose and the dagger are plot-critical.

After taking the dagger when the dagger was carried by the stranger, say “Troubled by thoughts of the conflict ahead, you reach for the dagger. Its heft is comforting.”

After taking the rose when the dagger was carried by the stranger, say “Thinking wistfully of times past, you reach for the rose. Its fragrance wafts gently skyward, and you inhale deeply.”

Warning given is a truth state that varies. [Has to be outside action variables, it seems, to be accessible to the reading a command activity.]

Before reading a command:
now warning given is false.

The taking action has a list of things called grab bag.
The taking action has a list of things called exclusory items.

Setting action variables for taking:
repeat with coveted item running through the multiple object list:
if coveted item is not plot-critical, add coveted item to grab bag;
otherwise add coveted item to exclusory items.

The Choice is a scene. The Choice begins when play begins.

Check taking a plot-critical thing while warning given is false and the number of entries in exclusory items is greater than 1 during the Choice (this is the intercept attempts to take multiple plot-critical things at once rule):
now warning given is true;
say “The stranger, seeing that you are about to take [the exclusory items with definite articles] catches your eagerly outstretched hand in an iron grip. ‘You must choose, and choose carefully,’ he warns. ‘The path you set foot on now is the path you must walk in the trials ahead.’” instead.

Check taking a plot-critical thing while warning given is true and the number of entries in exclusory items is greater than 1 during the Choice (this is the suppress attempts to take multiple plot-critical things after the first one rule):
stop the action.

A procedural rule when taking a plot-critical thing carried by the stranger during The Choice (this is the it’s OK to take what The Choice offers rule):
ignore the can’t take people’s possessions rule.

Every turn while the number of entries in the list of plot-critical things enclosed by the player is greater than 1 during The Choice (this is the emergency backup logic for plot-critical choice rule):
say “The stranger gently retrieves [the list of plot-critical things enclosed by the player] from your hands. ‘You can choose but one path,’ the stranger explains.”;
now the stranger carries every plot-critical thing.

The Choice ends when the player carries exactly one plot-critical thing.

When The Choice ends, say “The stranger nods. ‘Your path is chosen,’ he says.”

This is the silently announce items from multiple object lists rule:
unless taking a plot-critical thing while The Choice is happening:
if the current item from the multiple object list is not nothing, say “[current item from the multiple object list]: [run paragraph on]”.

The silently announce items from multiple object lists rule is listed instead of the announce items from multiple object lists rule in the action-processing rules.

test rose with “scenes / take all / take rose / take dagger”.
test dagger with “scenes / take all / take dagger / take rose”.[/code]

Does this code ever fail to produce the warning? “Before reading a command” happens every turn, so it seems to me that it would constantly reset “warning given” back to false, and never give the “suppress attempts to take multiple plot-critical things after the first one rule” a chance to fire. Unless I’m misunderstanding something.

The command “take all” lets you perform several taking actions during a single turn (and the before reading a command rule only resets the warning given variable before the first of them).

Ah, of course. Otherwise the message would be produced instead of taking the dagger, and then immediately again instead of taking the rose (which is part of the same turn, but not the same action). So the purpose is to produce the warning whenever you try to take both, but only once per turn. Thanks for clarifying that.

A very simple way to block taking all is :

understand “take all” as mistake (“You can only take 1 item at a time.”)

The simple way to do this is

understand “take all” as a mistake
(“You can only take 1 item at a time.”)

What about “take everything” or “get all” or “pick up all” or “take dagger and rose” or “take all except rose” or …

Yeah, never try to block a specific command using “Understand … as a mistake.” It’s – well – a mistake.

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.