THE ISSUE
It’s easy to configure an Action
to permit or prohibit use with “all”, via the actionAllowsAll
flag. It’s easy to change the failure message displayed when the player tries to use “all” with an action that doesn’t allow it. But by default there isn’t a way to change the failure message for specific actions.
This is another one of those things that seems like it would be an easy fix. Maybe a one-liner. But changing the behavior turns out to require a bit of a safari through the library.
WHY IT ISN’T A SIMPLE FIX
The stock message is output by a method on playerMessages
, so the simple fix would be something like:
modify playerMessages
// THIS DOESN'T WORK
allNotAllowed(actor) {
// Check for the action we want to display a custom message.
if(gAction.ofKind(SomeAction)) {
"<.parser>This is a placeholder custom
message.<./parser> ";
} else {
// This is the stock message.
"<.parser><q>All</q> cannot be used with that
verb.<./parser> ";
}
}
;
The problem here is that by the time the method is called and the output is displayed gAction
will not be the resolved action from the player’s input. It will be an instance of EventAction
generated inside the parser, and by default there is not (as far as I can tell) any way to recover the resolved action from the failure message method.
A SOLUTION
The approach I use in the module is to define a number of properties on Action
to hold pointers to libMessages
methods:
allNotAllowedMsg
for when the command uses “all” but this is prohibited by the actionnoMatchForAllMsg
for when the command uses “all” but no matching objects were found in scopenoMatchForAllButMsg
for when the command uses “all” with exceptions (i.e.>TAKE ALL BUT PEBBLE
) and this results in no matching objectsuniqueObjectRequiredMsg
for when the action requires a single object but noun resolution matched multiple objects (>KICK WALLS
)singleObjectRequiredMsg
the action requires a single object, but the command contains a list (>KICK PEBBLE, ROCK
).
Each of these properties on the Action
class/instance should be a pointer to a method on the appropriate parser message object (playerMessages
or npcMessages
), which has the same usage as the stock failure message method and should output a double-quoted string. Example:
modify playerMessages
cantSmellAll(actor) {
"<.parser>{You/He} can only smell one thing at a
time.<./parser> ";
}
;
modify SmellAction
allNotAllowedMsg = &cantSmellAll
actionAllowsAll = nil
;
This will produce:
>smell all
You can only smell one thing at a time.
THE MESSY DETAILS
Under the hood all of these messages are generated by BasicResolveResults
throwing an exception when one of its methods (allNotAllowed()
, for example) is called. The caller in this case will be one of the noun phrase productions (i.e. EverythingProd
), usually in its resolveNouns()
method.
The noun productions have access to the Action
instance held by the resolver, but by default it isn’t passed to the results object. If parsing succeeded the Action
would be available as a global (gAction
), but in this case it isn’t.
The module’s solution to this is to tweak the relevant productions to pass the Action
instance as an argument to the failure method (allNotAllowed()
or whatever), and then to also tweak BasicResolveResults
(and the relevant subclasses of it) so the methods accept the additional argument. The new argument is marked optional, so existing code shouldn’t need any modification to work with the new methods.
THE REPO
The code is here: perActionAllErrors github repo.
This is another one where the actual code is pretty minimal (it’s only around two hundred lines with comments) but it required waaay more digging than I was expecting it to, so I definitely wanted to stuff it into its own module with an explanation of how it all works. Because by this time next month I’m going to have forgotten all the details of how it works.