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:
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.
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.
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.
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).
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.
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.