1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*!
Chatter provides a macro that translates a human-readable script
into a format that can be easily used in Rust.
# Basic Usage
The following is an example of a basic [chatter] script:
``` plaintext
This is an example line
This line is followed by a choice:
- continue to branch 1
- switch to branch 2 -> branch_2
# branch_1
This line is in branch 1
-> end
# branch_2
This line is in branch 2
# end
```
Chatter parses the above script into a useful format, a [Chat].
However, how that chat is run is left to the user.
[Chatterbox](https://github.com/appybara13/chatterbox) is an example
of a cli runner.
The basic flow of running a chat follows:
``` rust
use chatter::{chatter, Chat, ChatContent, ChatPosition, Choice, Line};
fn main() {
let example_chat = chatter! {r#"
INSERT CHATTER SCRIPT HERE
"#};
run(&example_chat);
}
fn run(chat: &Chat) {
let mut current_position = ChatPosition::start();
while current_position != ChatPosition::end() {
current_position = step(chat, current_position);
}
}
fn step(chat: &Chat, position: ChatPosition) -> ChatPosition {
let content = match chat.get(position) {
Some(content) => content,
None => return ChatPosition::end(),
};
match content {
ChatContent::Line(line) => handle_line(line),
ChatContent::Choices(choices) => handle_choices(choices),
}
}
fn handle_line(line: &Line) -> ChatPosition {
// Present the line to the player
// Wait for timer/player to skip
// Return line.next()
todo!()
}
fn handle_choices(choices: &Vec<Choice>) -> ChatPosition {
// Present the choices to the player
// Wait for player to make a selection
// Return the selection choice's next()
todo!()
}
```
# Tags
To define more complex behaviour, you can use tags.
Tags can be applied to both lines and choices. A chatter script example is provided below.
``` plaintext
[Narrator] You attempt to open the door, but it doesn't budge.
- [Player, strength_over_14] Kick down the door.
- [Player, thievery_atleast_expert inventory_includes_lockpick] Pick the lock.
- [Player] Call out "Is anyone in there?"
- [Player] Walk way.
```
The list of tags for a line or choice can be obtained as strings.
More usefully, Chatter provides a [Tag] trait. Some of the above
choices include requirements. They could be represented by the following
type:
``` rust
use chatter::Tag;
enum Requirement {
Ability{ability: Ability, min: u32},
Skill{skill: Skill, level: SkillLevel},
Inventory{item: Item, count: u32}
}
impl Requirement {
fn fufilled(&self, player: &Player) -> bool { todo!() }
}
impl Tag for Requirement {
fn from(string: &str) -> Option<Self> { todo!() }
}
```
The list of requirements (ignoring other tags) can be be easily
obtained from a given [Choice]. Then, that choice could be offered
to the player only if all the requirements are met.
Chatter doesn't define this behaviour - the interpretation and use of tags is left
to the program using the library.
A non-exhaustive list of possible uses follows:
- Character names
- Art selection (A character might have different art for different facial expressions, for example)
- Text decoration (font, size, color, bold, italic)
- Choice requirements (As above, but could also be used for NPC dialogue; if the player has no gold say A, else say B)
- Choice randomization (tags could be used to assign weights)
- Game state changes (enter combat, invetory changes, relationship/alignment changes)
# Chatter Definitions
A **Line** is any line that doesn't start with **-** or **#**. If it starts with square brackets,
then the contents of the brackets are **Tag**s. Then, it must include text. **\[tag\] -> branch** is invalid.
A **Choice** starts with **-** but otherwise the follows the same rules as a **Line**. **Choice**s must exist in groups of at least two.
An empty line separates two seperate groups of **Choice**s. Any **Choice** without a **Goto** will be followed
by the next line after the group.
A **Branch** is a line that starts with **#** and acts as a marker for **Goto**s.
A **Goto** is marked by **->** and must be followed by a branch label that exists.
They be appended to the end of a **Line** or **Choice**, and indicate that the dialog
moves to the relevant branch after that **Line** or if that **Choice** is selected.
They can also be placed on a new line, applying to the whatever precedes it.
**Tag**s are separated by commas and whitespace and contained within square brackets,
at the start of a **Line** or **Choice**.
**Tag**s and **Branch** names can only contain alphanumeric characters and underscores.
Text in a **Line** or **Choice** can contain anything except a new line, or **->**. It also
cannot start with **-** or **#**.
Comments start with \/\* and end with \*\/.
**Line**s and **Choice**s don't overflow; a new line indications a new **Line** or **Choice**.
# A More Complex Example
A final, more complicated example of a chatter script follows:
``` plaintext
[Guard] By order of the Jarl, stop right there!
[Guard] You have commited crimes against Skyrim and her people. What say you in your defense?
- [Player] You caught me. I'll pay off my bounty. -> bounty
- [Player] I submit. Take me to jail. -> jail
- [Player, starts_combat] I'd rather die than go to prison!
- [Guard, random_weight_1.0 requires_imperial] Then suffer the Emperor's wrath.
- [Guard, random_weight_1.0 requires_stormcloak] Skyrim has no use for your kind.
- [Guard, random_weight_1.0] Then pay with your blood.
- [Guard, random_weight_2.0] That can be arranged.
- [Guard, random_weight_1.0] So be it.
-> end
# jail
[Guard, imprison_player] I guess you're smarter than you look.
-> end
# bounty
- [Guard, requires_player_male] Smart man.
- [Guard, requires_player_female] Smart woman.
[Guard] Now come along with us. We'll take any stolen goods, and you'll be free to go.
[Guard] After you pay the fine, of course.
# end
```
*/
/// Represents a whole dialogue, created with the [chatter] macro.
pub use Chat;
/// Either a [Line] or a Vec of [Choice]s.
pub use ChatContent;
/// Every [ChatContent] in a [Chat] has a position, and every [Line] and [Choice]
/// has a next position.
/// No [ChatContent] ever exists at ``ChatPosition::end()``.
pub use ChatPosition;
/// A Vec of choices represents a set of possible options, one of which can be taken.
/// They allow the dialogue to branch.
pub use Choice;
/// Represents a line of dialogue. A [Chat] that consists of just lines and no choices
/// will play the same every time.
pub use Line;
/// A trait that allows more ergonomic checking tags on a [Tagged] type.
pub use Tag;
/// Both [Line] and [Choice] can be tagged.
pub use Tagged;
/// The macro for parsing chatter script into a [Chat].
/// The script must be a raw string literal, ``r#"SCRIPT"#``.
pub use chatter;