Drop, take all

i had a question a while back about drop/take all and group actions. then, it involved invoking (tick) with each individual action in a group action.

despite reading up, i’m apparently still just as ignorant about group actions. this time my question is: how do you complete a group-action despite one of the $Simple actions failing?

i have a couple of items that the player cannot drop (trust me, it makes perfect sense in-game). so when a “drop all” happens, the group action fails when it hits one of these items and stops, leaving the player confused and still holding all the other items.

and the opposite - a “take all” will fail when the first untakeable item is reached, leaving everything else on the floor.

i would contend that both of these situations are counter-intuitive. i’ve read so many transcripts when a “take all” failed because of an untakeable object, the player continues on not realizing that the other takes all failed atoo, then can’t find the objects they thought they were carrying.

i haven’t been able to find the place to intercept this. i thought maybe:

(group-perform $MultiAction)
	(exhaust) {
		*(expand group-action $MultiAction into $Simple)
		(perform $Simple)
	}

had something to do with it but that doesn’t seem to fire with a “drop all”. plus, it’s an (exhaust) and shouldn’t stop on (fail) anyway.

is there a way to do this for “take/drop all” specifically? i wouldn’t want to change all group-actions as the default behavior does generally make sense most of the time.

1 Like

A failed action invokes (stop), which jumps out of the innermost (stoppable) environment, which is normally the one where all the actions from a given command are being performed.

So if you put your own (stoppable) around just the action you want, then only that environment will be stopped, not the outer one.

3 Likes

i’m still missing something.

just taking ‘drop all’ as an example - i put (stoppable) in all the (before/prevent/perform [drop $]) predicates in stdlib but nothing changes; if the first drop fails then the entire group-drop immediately stops.

Normally, (stop) is called in two circumstances:

  • By the library, if a (prevent $) rule succeeds
  • By the rule itself, if a (perform $) rule says “you can do that but it’s useless”

For taking/dropping, the first one is what matters. So you’ll want to put a (stoppable) around your call to (prevent $) (or around the call to (group-prevent $) which calls (prevent $) etc).

yes, that’s what i did above.

(group-prevent $MultiAction)
	*(expand group-action $MultiAction into $Simple)
	(stoppable)
		{
		(prevent $Simple)
		}

and also for (group-before) and (group-perform). i’ve basically scattered (stoppable)s liberally throughout the stdlib but nothing changes.

and i’m still having a hard time conceptually grasping why i’m using (stoppable) when i don’t want the group-action to stop.

It’s because of these two little bits in the standard library:

(stoppable) {
	(parse commandline $Words with choices [])
}
(group-instead of $MultiAction)
	~{ (group-prevent $MultiAction) (tick) (stop) }
	(group-perform $MultiAction)
	(exhaust) *(group-after $MultiAction)

In other words, every time the player provides input, handling that input is called inside a (stoppable) environment. That means if a (stop) comes anywhere in the handling of that input, the handling of the entire input is aborted. (The intent being, if you say something like TAKE KEY. N. N. N. W. N. NW. W, you probably don’t want to end up halfway across the map with no key if that first action failed.)

In particular, if (group-prevent $) succeeds, it calls (stop) automatically. (And by default, (group-prevent $) expands its input into a bunch of simple actions, and succeeds if (prevent $) succeeds for any of those.)

Now, you could just remove that (stop), if you wanted to, or use a multi-query to only (stop) if every simple action fails. That’s probably actually the best way to handle this, in your case. (Remove the (tick) too, if you do that.)

But the reason I originally recommended (stoppable) is because (stop) only breaks out of the innermost (stoppable) environment. So adding your own (stoppable) allows you to “catch” that (stop) and prevent it from stopping more things.

3 Likes

thanks. that’s what i needed.

i used (stoppable) in (instead of $Action) which works, but obviously works for all actions.

this:

(instead of $Action)
	($Obj is one of $Action)
	(if) { ($Obj = @drop) (or) ($Obj = @take) } (then)
		~{ (prevent $Action) }
	(else)
		~{ (prevent $Action) (tick) (stop) }
	(endif)
	(perform $Action)
	(exhaust) *(after $Action)

is kind of ugly and un-dialog-ish but seems to work and limit its damage to ‘take/drop all’.

we’ll see what kind of unanticipated issues this causes in game play down the road…

thx!

1 Like

Here’s perhaps a more Dialog-ish solution, though I haven’t tested it properly.

(group-prevent $MultiAction)
	(collect $Simple)
		*(expand group-action $MultiAction into $Simple)
	(into $Actions)
	(accumulate 1)
		*($Simple is one of $Actions)
		(prevent $Simple)
	(into $Failures)
	(length of $Actions into $Total)
	($Total = $Failures)

In other words, this runs the prevent-rules on every individual simple action, and only succeeds (thus stopping processing) if all of them failed.

The even-more-Dialog-ish solution would be to write a new predicate that recursively tests if a closure succeeds for every element of a list, but that seems like overkill for this particular problem if it doesn’t exist already.