Basic Verb Use:Action Hooks
Verb actions provide a hook for authors to inject custom code into the doTry and doSuccess phases of a verb. Verb actions can be used to augment or override a verb's built-in logic.
#TMI Timing of action hook calls
The precise timing of calls to action hooks - meaning, when they occur in a verb's lifecycle - may vary a bit from verb to verb. Each verb's doTry and doSuccess code blocks will make action calls, but they're placed according to the individual logic of the verb. Usually, action calls are placed as close as possible to the top of a block, so that an author may override the entire block if they choose. Sometimes, actions may fall a little lower in the block; for example, if the verb needs to resolve some issue involving the order of words in the input before moving on to processing the nouns. This is just a mild warning to say that verb actions may not behave with perfect consistentcy from verb to verb.
Here's an example of how you might hook an asset into a verb action. In this example, tryThrowThis, tryThrowThisAtThat, doThrowThis and doThrowThisAtThat are all actions of throw. You can find each verb's actions listed on its doc page.
MyGame.createAsset({
class: "NPC",
name: "cave troll",
place: {in: "cave"},
});
MyGame.createAsset({
class: "Weapon",
name: "throwing star",
dov: {
throw: {
// Because we're defining a direct object verb subscription,
// the "anything" in with_anything refers to an indirect object.
// This lets a player throw the star at anything.
with_anything: true,
},
},
descriptions: {
look: "It's a little star of black metal with sharpened points. ",
// descriptions.throw is one way to print a string when the asset is thrown
// If you also use verb actions as shown below,
// you may find the output competes with itself.
// You can choose which option works the best for you.
throw: "It bounces across the floor with a series of metallic clangs. ",
},
article: "the",
place: { in: "weapons rack" },
tryThrowThis: function(params)
{
// this results if the player tries to throw the star
// it will print independently of any other output the verb prints
let msg = "You limber up your arm for a good overhand throw. ";
MyGame.print(msg);
},
tryThrowThisAtThat:
{
"cave troll": function(params)
{
// this results only if the player tries to throw the star at the troll
let msg = "You size up the troll and ready your arm for a strong throw. ";
MyGame.print(msg);
}
},
doThrowThis: function(params)
{
// this results if the player succeeds in throwing the star
let msg = "You feel a painful twinge in your arm after releasing the star. ";
MyGame.print(msg);
},
doThrowThisAtThat:
{
"cave troll": function(params)
{
// this results only if the player succeeds in throwing the star at the troll
let msg = "The star thuds to a stop in the troll's chest. The troll looks annoyed. ";
MyGame.print(msg);
}
}
});
Phase HooksVerb Phases
Verb Actions
Verb actions exist on the asset.
They're expected to return text, but they can also execute code.
If text is return it will be appended to output.
Returning TRUE or no value (aka UNDEFINED) lets the verb continue
Returning NULL ends operations for this asset. If multiple actions are queued, operations will continue.
Returning FALSE ends operations for all queued actions as well as this one.
WHY SO MANY???
Verb action hooks provide hooks that allow authors to inject custom code in response to specific combinations of verb/preposition/noun. There are a lot of them and clearly some are redundant; in its defense, it's a deliberate effort to offer a menu of precise injection points for authors' custom code. To use a verb action, just use the verb action name as a method on any asset. Below is a generic example of how to code a verb action.
Expand for example
In this example, the pistol asset has two verb actions.
-
pistol.doShootThis()will be called when a player inputs "shoot pistol". -
pistol.doShootThatWithThis.television()will be called when a player inputs "shoot television with pistol".
MyGame.createAsset({
class: "Player",
name: "Elvis",
});
MyGame.createAsset({
class: "Weapon",
name: "pistol",
doShootThis: {
let msg = `You fire the pistol! BANG! `;
MyGame.print(msg);
},
doShootThatWithThis:{
"television": function {
let msg = `You fire the pistol at the television! BANG!
The television explodes with sparks and a screech of static. `;
MyGame.print(msg);
},
},
});
MyGame.createAsset({
class: "Electronics",
name: "television",
});
Verb actions are called by the verb.handleActions()
method, which looks for those nested functions and calls whatever function it
finds. Each verb has a unique set of actions, which mirror the
sentence structures the verb can handle. For
instance, the verb lock handles "verb noun" and
"verb noun preposition noun", and so it handles
tryLockThis and
doLockThis and
tryLockThisWithThat and
doLockThisWithThat.
The difference between try actions and
do actions is one of timing.
try actions fire immediately before a verb's
doTry phase, which provides an opportunity to supersede
a verb's default conditional logic before it tests whether the verb can be
applied to the assets. do actions fire
immediately before a verb's doSuccess phase, which
provides an opportunity to supersede or append the verb's state changes and
output to the player.
It's common for sentence structures to be mutated during a verb's
doTry phase, as doTry may reorder a player's input
to make it conform with the verb's logic. This means that the
try and do
actions may differ within the same turn. You can check the browser's
Javascript console to see which actions are being called.
snake_case vs camelCase
Action hooks come in two flavors: snake_case and
camelCase. Though these are very similar, there is one important
distinction.
-
snake_casemethods are expected to return a string, and that string will be used to override the default output of this verb. This is the simpler version, for authors who only need to work with strings.
camelCase methods are not expected to return a string,
and instead are expected to run custom code. What that custom code does is up
to the author, and it may independently override the turn's output, but is not
required to. This is the more complex version, for authors who want greater
control over the output and effects of a verb.
In the following example, we'll create a fox NPC with methods that handle ask fox about x and tell fox about x. In this example, telling the fox only requires a simple response, because the fox just doesn't care; but asking the fox a question requires logic that looks to see what room the player is in, so the fox can respond differently depending on the current room.
// notice how we're saving a reference to the fox asset
// we'll use that reference later
const fox = MyGame.createAsset({
class: "NPC",
name: "fox",
synonyms: [],
description: `The fox is thin and patchy. `,
place: { in: "Desert" },
gender: "nonbinary",
proper_name: "Reynard",
use_proper_name: false, // lets parser say "the fox"
use_definite_article: true, // lets parser say "the fox"
// do_tell_this_about_that, in snake_case, is expected
// to return a string that will override the default output
// for the turn. This is the simpler form, meant for authors
// who only want to write strings.
do_tell_this_about_that: `"Do tell," says the fox. `,
// doAskThisAboutThat, in camelCase, is not expected to return
// a string. It's up to the author to handle what happens next.
// In this case, we're going to use game.overrideOutput(),
// which does in fact override the turn's output, but
// we could run any other code that we wanted here
// and completely customize the effects of the verb.
doAskThisAboutThat: {
// Each thing that we want to ask the fox about
// gets a unique block, so that we can run
// independent logic per object.
mirage: function () {
let msg = "";
if (MyGame.getRoom().name === "Desert") {
msg = `"Why yes, I know all about that," says the fox. `;
} else {
msg = `"No need to worry about that anymore, bub." `;
}
DesertDrifter.overrideOutput(msg);
};
},
});
// We can also add additional blocks to doAskThisAboutThat
// after the fact. Remember how we saved the created asset
// to a variable name, fox? Now we can use fox as a reference
// back to the game asset.
fox.doAskThisAboutThat.sandstorm = function () {
let msg = "";
if (this.getRoom().name === "Sandstorm") {
msg = `"Why yes, I know all about that," says the fox. `;
} else {
msg = `"No need to worry about that anymore, bub." `;
}
MyGame.overrideOutput(msg);
};