Skip to content

Commit

Permalink
feat: Added the <<character>> command to Jenny (#2274)
Browse files Browse the repository at this point in the history
The new command allows pre-declaring the characters that will be seen in the yarn scripts, and provides a place to store any additional information associated with each character.

The DialogueLine.character property now returns a Character object, instead of a String.
  • Loading branch information
st-pasha authored Jan 14, 2023
1 parent ab02823 commit 6548e9c
Show file tree
Hide file tree
Showing 28 changed files with 478 additions and 42 deletions.
2 changes: 1 addition & 1 deletion doc/_sphinx/extensions/yarn_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class YarnLexer(RegexLexer):
(r'\$\w+', Name.Variable),
(r'([+\-*/%><=]=?)', Operator),
(r'\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?', Number),
(r'[(),]', Punctuation),
(r'[(),]|\.\.\.', Punctuation),
(r'"', String.Delimeter, 'string'),
(r'\w+', Name.Function),
(r'.', Error),
Expand Down
60 changes: 60 additions & 0 deletions doc/other_modules/jenny/language/commands/character.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# `<<character>>`

The **\<\<character\>\>** command declares a character with the given name, and one or more aliases
that can be used in the scripts.

The command has several purposes:

- it protects you from accidentally misspelling a character's name in your script;
- it allows a character to have *full name*, which doesn't have to be an ID;
- it allows declaring multiple aliases for the same character, which can be used in different
nodes (an alias may even be in a different language than the full name);
- you can associate additional data with each character, which will then be available at runtime.

The format of this command is the following:

```yarn
<<character "FULL NAME" alias1 alias2...>>
```

The *full name* here is optional: if given, it will be considered *the* name of the character.
However, if the name is omitted, then the first alias will be considered the true character's name.
Each *alias* must be a valid ID, and at least one alias must be provided. For example:

```yarn
// A well-mannered seven-year-old girl, who nevertheless always gets into
// all kinds of zany adventures.
<<character Alice>>
// A magical cat known for his ability to grin majestically, and partially
// vanish. He is mad (by his own admission).
<<character "Cheshire Cat" Cat Cheshire>>
// A foul-tempered Queen, who is also a playing card. Described as
// "a blind fury", her favorite saying is "Off with their heads!".
// Not to be confused with Red Queen.
<<character "Queen of Hearts" Queen QoH QH>>
```

After a character is declared, any of its aliases can be used in the script: they will all refer
to the same `Character` object. At the same time, using a character without declaring it first is
not allowed (unless a special flag in `YarnProject` is set to allow this).

```yarn
title: Alice_and_the_Cat
---
Alice: But I don't want to go among mad people.
Cat: Oh, you can't help that, we're all mad here. I'm mad. You're mad.
Alice: How do you know I'm mad?
Cat: You must be, or you wouldn't have come here.
Alice: And how do you know that you're mad?
Cat: To begin with, a dog's not mad. You grant that?
Alice: I suppose so.
Cat: Well then, you see a dog growls when it's angry, and wags its tail \
when it's pleased.
Cat: Now, [i]I[/i] growl when I'm pleased, and wag my tail when I'm angry. \
Therefore, I'm mad.
Alice: [i]I[/i] call it purring, not growling.
Cat: Call it what you like.
===
```
4 changes: 4 additions & 0 deletions doc/other_modules/jenny/language/commands/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ scripts. For a full description of these commands, see the document on [user-def

### Variables

**[\<\<character\>\>](character.md)**
: Declares a character (person).

**[\<\<declare\>\>](declare.md)**
: Declares a global variable.

Expand Down Expand Up @@ -50,6 +53,7 @@ scripts. For a full description of these commands, see the document on [user-def
```{toctree}
:hidden:
<<character>> <character.md>
<<declare>> <declare.md>
<<if>> <if.md>
<<jump>> <jump.md>
Expand Down
18 changes: 13 additions & 5 deletions doc/other_modules/jenny/language/lines.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ in a [node body]. A line may contain the following elements:

A **line** is represented with the [DialogueLine] class in Jenny runtime.

[node body]: nodes.md#body
[DialogueLine]: ../runtime/dialogue_line.md


## Character ID

Expand Down Expand Up @@ -62,6 +59,11 @@ Attention\: The cake is NOT a lie
===
```

```{note}
All characters must be **declared** using the [\<\<character\>\>] command
before they can be used in a script.
```


## Interpolated expressions

Expand Down Expand Up @@ -101,7 +103,7 @@ further processing. Which means that the text of the expression may contain spec
(such as `[`, `]`, `{`, `}`, `\`, etc), and they don't need to be escaped. It also means that the
expression cannot contain markup, or produce a hashtag, etc.

Read more about expressions in the [Expressions](expressions/expressions.md) section.
Read more about expressions in the [Expressions] section.


## Markup
Expand All @@ -122,7 +124,7 @@ additional information attached to the line that shows that the last 17 characte
the `em` tag.

Markup tags can be nested, or be zero-width, they can also include parameters whose values can be
dynamic. Read more about this in the [Markup](markup.md) document.
dynamic. Read more about this in the [Markup] document.


## Hashtags
Expand Down Expand Up @@ -184,3 +186,9 @@ This line is so long that it becomes uncomfortable to read in a text editor. \
text.
===
```


[node body]: nodes.md#body
[DialogueLine]: ../runtime/dialogue_line.md
[Expressions]: expressions/expressions.md
[Markup]: markup.md
29 changes: 29 additions & 0 deletions doc/other_modules/jenny/runtime/character.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Character

A **Character** represents a person who is speaking a particular line in a dialogue. This object
is available as the `.character` property of a [DialogueLine] delivered to your [DialogueView].


## Properties

**name** `String`
: The canonical name of the character, as declared by the [\<\<character\>\>] command.

**aliases** `List<String>`
: Additional names (IDs) that may be used for this character in yarn scripts.

**data** `Map<String, dynamic>`
: Extra information that you can associate with this character. This may include their short bio,
portrait, affiliation, color, etc. This information must be stored for each character manually,
and then it will be accessible from [DialogueView]s.


## See Also

- [CharacterStorage]: the container where all Character objects within a YarnProject are cached.


[\<\<character\>\>]: ../language/commands/character.md
[CharacterStorage]: character_storage.md
[DialogueView]: dialogue_view.md
[DialogueLine]: dialogue_line.md
22 changes: 22 additions & 0 deletions doc/other_modules/jenny/runtime/character_storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# CharacterStorage

The **CharacterStorage** object is a cache of all [Character]s declared in yarn scripts. Typically,
this cache will be populated with the help of the [\<\<character\>\>] commands. Adding characters
manually is possible but not recommended.


## Methods

**contains**(`String name`) → `bool`
: Returns `true` if a character with the given name or alias was defined.

**operator[]**(`String name`) → `Character?`
: Returns the [Character] object with the given name/alias, or `null` if this character was not
defined.

**add**(`Character character`)
: Adds a new `Character` object into the storage.


[\<\<character\>\>]: ../language/commands/character.md
[Character]: character.md
2 changes: 1 addition & 1 deletion doc/other_modules/jenny/runtime/dialogue_line.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The **DialogueLine** class represents a single [Line] of text in the `.yarn` scr

## Properties

**character** `String?`
**character** `Character?`
: The name of the character who is speaking the line, or `null` if the line has no speaker.

**text** `String`
Expand Down
1 change: 1 addition & 0 deletions doc/other_modules/jenny/runtime/dialogue_option.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ options will be grouped into [DialogueChoice] objects.
**isDisabled** `bool`
: Same as `!isAvailable`.


[Option]: ../language/options.md
[DialogueChoice]: dialogue_choice.md
2 changes: 2 additions & 0 deletions doc/other_modules/jenny/runtime/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
```{toctree}
:hidden:
Character <character.md>
CharacterStorage <character_storage.md>
CommandStorage <command_storage.md>
DialogueChoice <dialogue_choice.md>
DialogueLine <dialogue_line.md>
Expand Down
12 changes: 12 additions & 0 deletions doc/other_modules/jenny/runtime/yarn_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ final yarn = YarnProject()

All custom commands must be added before they can be used in the dialogue script.

**characters** `CharacterStorage`
: The [container][CharacterStorage] for all [Character] objects declared in your yarn scripts.

**strictCharacterNames** `bool`
: If `true` (default), the validity of character names will be strictly enforced. That is, all
characters must be declared before they can be used, using the [\<\<character\>\>] commands. If
this property is set to false, then new [Character] objects will be created automatically as
they are encountered in scripts.

**trueValues**, **falseValues** `Set<String>`
: The strings that can be recognized as `true`/`false` values respectively.

Expand All @@ -77,6 +86,9 @@ final yarn = YarnProject()
existing ones.


[\<\<character\>\>]: ../language/commands/character.md
[Character]: character.md
[CharacterStorage]: character_storage.md
[CommandStorage]: command_storage.md
[FunctionStorage]: function_storage.md
[Node]: node.md
2 changes: 2 additions & 0 deletions packages/flame_jenny/jenny/lib/jenny.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export 'src/character.dart' show Character;
export 'src/character_storage.dart' show CharacterStorage;
export 'src/command_storage.dart' show CommandStorage;
export 'src/dialogue_runner.dart' show DialogueRunner;
export 'src/dialogue_view.dart' show DialogueView;
Expand Down
22 changes: 22 additions & 0 deletions packages/flame_jenny/jenny/lib/src/character.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// A [Character] represents a particular person who is speaking a line in a
/// dialogue. All characters must be declared with the help of the
/// `<<character>>` command.
class Character {
Character(this.name, {List<String>? aliases}) : aliases = aliases ?? [];

Map<String, dynamic>? _data;

/// The canonical name of the character
final String name;

/// The list of aliases
final List<String> aliases;

/// Additional information associated with this character.
///
/// You can store any key-value pairs here that you want, or nothing at all.
Map<String, dynamic> get data => _data ??= <String, dynamic>{};

@override
String toString() => 'Character($name)';
}
28 changes: 28 additions & 0 deletions packages/flame_jenny/jenny/lib/src/character_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:jenny/src/character.dart';

/// The container for all [Character]s defined in your yarn scripts. This
/// container is populated as the YarnProject parses the input scripts.
class CharacterStorage {
final Map<String, Character> _cache = {};

bool get isEmpty => _cache.isEmpty;
bool get isNotEmpty => _cache.isNotEmpty;

/// Was the character with the given name or alias added to this container?
bool contains(String name) => _cache.containsKey(name);

/// Retrieves the character given its name/alias, or returns `null` if such
/// character is not present.
Character? operator [](String name) => _cache[name];

/// Adds a new [character] to the container.
///
/// This is mostly intended for internal use; in yarn scripts use command
/// `<<character>>` to declare characters.
void add(Character character) {
_cache[character.name] = character;
for (final alias in character.aliases) {
_cache[alias] = character;
}
}
}
Loading

0 comments on commit 6548e9c

Please sign in to comment.