-
-
Notifications
You must be signed in to change notification settings - Fork 899
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Arguments of a UserDefinedCommand are now accessible (#2224)
Class UserDefinedCommand now exposes argumentString and arguments properties, allowing them to be queried in a DialogueView; Make sure the arguments of a user-defined command are computed only once per invocation; Added documentation for user-defined commands; addDialogueCommand renamed into addOrphanedCommand.
- Loading branch information
Showing
17 changed files
with
423 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
doc/other_modules/jenny/language/commands/user_defined_commands.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# User-defined commands | ||
|
||
In addition to the built-in commands, you can also declare your own **user-defined commands** for | ||
use in your yarn scripts. Typically, these commands would perform some in-game action that can be | ||
viewed as a natural part of the dialogue. For example, you can create commands for such action as | ||
`<<wave>>`, `<<smile>>`, `<<frown>>`, `<<moveCamera>>`, `<<zoom>>`, `<<shakeCamera>>`, | ||
`<<fadeOut>>`, `<<walk>>`, `<<give>>`, `<<take>>`, `<<achievement>>`, `<<GainExperience>>`, | ||
`<<startQuest>>`, `<<finishQuest>>`, `<<openTrade>>`, `<<drawWeapon>>`, and so on. | ||
|
||
In many cases, the commands will need to take arguments. The arguments of a user-defined command | ||
are processed according to the following rules: | ||
|
||
- First, all content after the command name and until the closing `>>` is parsed according to the | ||
rules of regular line parsing, where interpolated expressions are allowed but markup and hashtags | ||
are not. | ||
- At runtime, the content of that line is evaluated, meaning that we substitute the values of all | ||
expressions. | ||
- The evaluated argument string is then broken into individual arguments at whitespace, and the | ||
types of these arguments are checked against the signature of the backing function. | ||
- Then, the backing function is called with the parsed arguments. | ||
- Lastly, all dialogue views in the dialogue runner receive the `onCommand()` event. | ||
|
||
As a concrete example, consider the following command: | ||
|
||
```yarn | ||
<<give Gold {round(100 * $multiplier)}>> | ||
``` | ||
|
||
First note that, unlike builtin commands, the arguments of the command are treated as text, and any | ||
expressions need to be placed in curly brackets. | ||
|
||
Then, at runtime the expression is evaluated, and (assuming `$multiplier` is 1.5) the command's | ||
argument string becomes `"Gold 150"`. The string is then broken at white spaces and each argument | ||
is parsed according to its type in the backing Dart function. For example, if the function's | ||
signature is `void give(String item, int? amount)`, then it will be invoked as `give("Gold", 150)`. | ||
If, on the other hand, the number or types of arguments do not match the expected signature, then | ||
a `DialogueException` will be raised. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
# CommandStorage | ||
|
||
The **CommandStorage** is a part of [YarnProject] responsible for storing all [user-defined | ||
commands]. You can access it as the `YarnProject.commands` property. | ||
|
||
The command storage can be used to register any number of custom commands, making them available to | ||
use in yarn scripts. Such commands must be registered before parsing the yarn scripts, or the | ||
compiler will throw an error that the command is not recognized. | ||
|
||
In order to register a function as a yarn command, the function must satisfy several requirements: | ||
|
||
- The function's return value must be `void` or `Future<void>`. If the function returns a future, | ||
then that future will be awaited before proceeding to the next step of the dialogue. This makes it | ||
possible to create commands that take a certain time to unfold in the game, for example | ||
`<<walk>>`, `<<moveCamera>>`, or `<<prompt>>`. | ||
- The function's arguments must be of types that are known to Yarn: `String`, `num`, `int`, | ||
`double`, or `bool`. All arguments must be positional, with no defaults. | ||
- In order to register the function, use methods `addCommand0()` ... `addCommand3()`, according to | ||
the number of function's arguments. | ||
- If the function's signature has 1 or more booleans at the end, then those arguments will be | ||
considered optional and will default to `false`. | ||
|
||
|
||
## Properties | ||
|
||
**trueValues**, **falseValues** `Set<String>` | ||
: The strings that can be recognized as `true`/`false` values respectively. | ||
|
||
|
||
## Methods | ||
|
||
**hasCommand**(`String name`) → `bool` | ||
: Returns the status of whether the command `name` has been added to the storage. | ||
|
||
**addCommand0**(`String name`, `FutureOr<void> Function() fn`) | ||
: Registers a no-argument function `fn` as the command `name`. | ||
|
||
**addCommand1**(`String name`, `FutureOr<void> Function(T1) fn`) | ||
: Registers a single-argument function `fn` as the command `name`. | ||
|
||
**addCommand2**(`String name`, `FutureOr<void> Function(T1, T2) fn`) | ||
: Registers a two-argument function `fn` as the command `name`. | ||
|
||
**addCommand3**(`String name`, `FutureOr<void> Function(T1, T2, T3) fn`) | ||
: Registers a three-argument function `fn` as the command `name`. | ||
|
||
**addOrphanedCommand**(`name`) | ||
: Registers a command `name` which is not backed by any Dart function. Such command will still be | ||
delivered to [DialogueView]s via the `onCommand()` callback, but its arguments will not be parsed. | ||
|
||
|
||
## Examples | ||
|
||
|
||
### `<<StartQuest>>` | ||
|
||
Suppose we want to have a yarn command `<<StartQuest>>`, which would initiate a quest. The command | ||
would take the quest name and quest ID as arguments. Technically, just the ID should be enough -- | ||
but then it would be really difficult to read the yarn script and understand what quest is being | ||
initiated. So, instead we'll pass both the ID and the name, and then check at runtime that the ID | ||
of the quest matches its name. | ||
|
||
A typical invocation of this command might look like this (note that the name of the quest is in | ||
quotes, otherwise it would be parsed as four different arguments `"Get"`, `"rid"`, `"of"`, and | ||
`"bandits"`): | ||
|
||
```yarn | ||
<<StartQuest Q037 "Get rid of bandits">> | ||
``` | ||
|
||
In order to implement this command, we create a Dart function `startQuest()` with two string | ||
arguments. The function will do a brief animated "Started quest X" message, but we don't want the | ||
game to dialogue to wait for that message, so we'll make the function return `void`, not a future. | ||
Finally, we register the command with `commands.addCommand2()`. | ||
|
||
```dart | ||
class MyGame { | ||
late YarnProject yarnProject; | ||
void startQuest(String questId, String questName) { | ||
assert(quests.containsKey(questId)); | ||
assert(quests[questId]!.name == questName); | ||
// ... | ||
} | ||
Future<void> onLoad() async { | ||
yarnProject = YarnProject() | ||
..commands.addCommand2('StartQuest', startQuest); | ||
} | ||
} | ||
``` | ||
|
||
Note that the name of the Dart function is different from the name of the command -- you can choose | ||
whatever names suit your programming style best. | ||
|
||
|
||
### `<<prompt>>` | ||
|
||
The `<<prompt>>` function will open a modal dialogue and ask the user to enter their response. This | ||
command will be waiting for the user's input, so it must return a future. Also, we want to return | ||
the result of the prompt into the dialogue -- but, unfortunately, the commands are not expressions, | ||
and are not supposed to return values. So instead we will write the result into a global variable | ||
`$prompt`, and then the dialogue can access that variable in order to read the result of the prompt. | ||
|
||
```dart | ||
class MyGame { | ||
final YarnProject yarnProject = YarnProject(); | ||
Future<void> prompt(String message) async { | ||
// This will wait until the modal dialog is popped from the router stack | ||
final name = await router.pushAndWait(KeyboardDialog(message)); | ||
yarnProject.variables.setVariable(r'$prompt', name); | ||
} | ||
Future<void> onLoad() async { | ||
yarnProject | ||
..variables.setVariable(r'$prompt', '') | ||
..commands.addCommand1('prompt', prompt); | ||
} | ||
} | ||
``` | ||
|
||
Then in a yarn script this command can be used like this: | ||
|
||
```yarn | ||
<<declare $name as String>> | ||
title: Greeting | ||
--- | ||
Guide: Hello, my name is Jenny, and you? | ||
<<prompt "Enter your name:">> | ||
<<set $player = $prompt>> // Store the name for later | ||
Guide: Nice to meet you, {$player} | ||
=== | ||
``` | ||
|
||
|
||
### `<<give>>` | ||
|
||
Suppose that we want to make a command that will give the player a certain item, or a number of | ||
items. This command would take 3 arguments: the person who gives the items, the name of the item, | ||
and the quantity. For example: | ||
|
||
```yarn | ||
<<give {$quest_reward} TraderJoe>> | ||
``` | ||
|
||
Note that the quest reward variable will contain both the reward item and its amount, for example | ||
it could be `"100 gold"`, `"5 potion_of_healing"`, or `'1 "Sword of Darkness"'`. When such | ||
variable is substituted into the command at runtime, the command becomes equivalent to | ||
|
||
```yarn | ||
<<give 100 gold TraderJoe>> | ||
<<give 5 potion_of_healing TraderJoe>> | ||
<<give 1 "Sword of Darkness" TraderJoe>> | ||
``` | ||
|
||
which will then be parsed as a regular 3-argument command corresponding to the following Dart | ||
function: | ||
|
||
```dart | ||
/// Takes [amount] of [item]s from [source] and gives them to the player. | ||
void give(int amount, String item, String source) { | ||
// ... | ||
} | ||
``` | ||
|
||
|
||
## See also | ||
|
||
- The description of [user-defined commands] in the YarnSpinner language. | ||
- The [UserDefinedCommand] class, which is used to inform a [DialogueView] that a custom command | ||
is being executed. | ||
|
||
|
||
[DialogueView]: dialogue_view.md | ||
[UserDefinedCommand]: user_defined_command.md | ||
[YarnProject]: yarn_project.md | ||
[user-defined commands]: ../language/commands/user_defined_commands.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# UserDefinedCommand | ||
|
||
The **UserDefinedCommand** class represents a single invocation of a custom (non-built-in) command | ||
within a yarn script. Objects of this type will be delivered to a [DialogueView] in its | ||
`.onCommand()` method. | ||
|
||
|
||
## Properties | ||
|
||
**name** `String` | ||
: The name of the command, without the angle brackets. For example, if the command is `<<smile>>` | ||
in the yarn script, then its name will be `"smile"`. | ||
|
||
**argumentString** `String` | ||
: Command arguments, as a single string. For example, if the command is `<<move Hippo {$delta}>>`, | ||
and the value of variable `$delta` is `3.17`, then the argument string will be `"Hippo 3.17"`. | ||
|
||
The `argumentString` is re-evaluated every time the command is executed, however, it is an error | ||
to access this property before the command was executed by the dialogue runner. | ||
|
||
**arguments** `List<dynamic>?` | ||
: Command arguments, as a list of parsed values. This property will be null if the command was | ||
declared without a signature (i.e. as an "orphaned command"). However, if the command was linked | ||
as an external function, then the number and types of arguments in the list will correspond to | ||
the arguments of that function. | ||
|
||
In the same example as above, the `arguments` will be `['Hippo', 3.17]`, assuming the linked Dart | ||
function is `move(String target, double distance)`. | ||
|
||
|
||
## See also | ||
|
||
- The description of [User-defined Commands] in the YarnSpinner language. | ||
- The guide on how to register a new custom command in the [CommandStorage] document. | ||
|
||
|
||
[CommandStorage]: command_storage.md | ||
[DialogueView]: dialogue_view.md | ||
[User-defined Commands]: ../language/commands/user_defined_commands.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.