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
227
228
229
230
231
232
233
234
235
236
237
//! [Replique] dialogues, as a Bevy plugin.
//!
//! [`replique`] parses and runs the dialogue; this crate wires it into Bevy.
//! A `.rep` file is an asset, a running dialogue is an entity, and the game
//! talks to it with messages.
//!
//! [Replique]: https://github.com/Galdormin/replique
//!
//! # A dialogue
//!
//! Dialogues are plain text. Lines, choices and jumps make up most of a file;
//! `[let]` holds state, `[if]` and `[while]` shape what gets played, and
//! anything between `[` and `]` inside a line is evaluated and inserted where
//! it stands. `assets/dialogue/shop.rep`:
//!
//! ```text
//! := shop
//! Merchant: Welcome! You have [gold()] coins.
//!
//! [if gold() >= 10]
//! Merchant: The lantern is yours.
//! >> buy("lantern")
//! [else]
//! Merchant: Come back when you can pay.
//!
//! [let $bells = 3]
//! [while $bells > 0]
//! [let $bells = $bells - 1]
//! >> await ring_bell()
//!
//! -> Thanks!
//! Merchant: Any time.
//! -> Say nothing
//! Merchant: ...
//! ---
//! ```
//!
//! `gold()` and `buy(...)` are the game's own, registered as Bevy systems — see
//! [Commands and functions](#commands-and-functions) below. The full format is
//! documented in [`replique`].
//!
//! # Setup
//!
//! Add [`RepliquePlugin`], load a dialogue, spawn a [`DialogueRunner`] for it
//! and send it a [`StartDialogue`]. The asset does not have to be loaded yet:
//! the runner holds the start pending until it is.
//!
//! ```rust,no_run
//! use bevy::prelude::*;
//! use bevy_replique::prelude::*;
//!
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(RepliquePlugin)
//! .add_systems(Startup, setup)
//! .add_systems(Update, show_line)
//! .run();
//! }
//!
//! fn setup(
//! mut commands: Commands,
//! assets: Res<AssetServer>,
//! mut start: MessageWriter<StartDialogue>,
//! ) {
//! let dialogue: Handle<RepliqueDialogue> = assets.load("dialogue/shop.rep");
//! let runner = commands.spawn(DialogueRunner::new(dialogue)).id();
//!
//! start.write(StartDialogue {
//! runner,
//! node: "start".into(),
//! });
//! }
//!
//! /// Show every line, then ask the runner to carry on.
//! fn show_line(
//! mut lines: MessageReader<DialogueLine>,
//! mut resume: MessageWriter<ResumeDialogue>,
//! ) {
//! for line in lines.read() {
//! match &line.speaker {
//! Some(speaker) => println!("{speaker}: {}", line.text),
//! None => println!("{}", line.text),
//! }
//!
//! resume.write(ResumeDialogue {
//! token: line.token,
//! input: ResumeInput::Advance,
//! });
//! }
//! }
//! ```
//!
//! # Driving a dialogue
//!
//! A runner suspends every time it needs the game, and reports why:
//! [`DialogueLine`] for a line, [`DialogueChoices`] for a choice point,
//! [`DialogueCommand`] for a `>>` command, [`DialogueFinished`] at the end.
//!
//! Each of them carries a [`DialogueToken`], which a [`ResumeDialogue`] has to
//! hand back to move the dialogue on — [`ResumeInput::Advance`] after a line or
//! a command, [`ResumeInput::Select`] after choices. A runner mints a new token
//! every time it moves, so an answer that arrives late (a second press on a
//! line, an animation ending after the player skipped it) is dropped instead
//! of advancing whatever the dialogue has reached in the meantime.
//!
//! Because every message names its runner, several dialogues can run at once.
//!
//! The `#tags` of a line or of a choice, as in `Alice: #angry Give it back!`,
//! come with it in [`DialogueLine::tags`] and [`DialogueChoice::tags`].
//!
//! # Commands and functions
//!
//! A dialogue reaches into the game in two ways. A `>> command(...)` *does*
//! something and may leave the dialogue waiting; a `[function(...)]` *answers*
//! something and never can. Both are plain Bevy systems, taking their arguments
//! already typed through `In`:
//!
//! ```rust
//! # use bevy::prelude::*;
//! # use bevy::platform::collections::HashSet;
//! use bevy_replique::prelude::*;
//!
//! #[derive(Resource, Default)]
//! struct Flags(HashSet<String>);
//!
//! /// `>> set_flag("met_alice", true)`
//! #[replique_command]
//! fn set_flag(In((flag, on)): In<(String, bool)>, mut flags: ResMut<Flags>) {
//! if on {
//! flags.0.insert(flag);
//! } else {
//! flags.0.remove(&flag);
//! }
//! }
//!
//! /// `Alice: You have [gold()] coins.`
//! #[replique_function]
//! fn gold(In(()): In<()>, flags: Res<Flags>) -> i64 {
//! flags.0.len() as i64
//! }
//!
//! # let mut app = App::new();
//! # app.init_resource::<Flags>();
//! app.add_dialogue_command(set_flag)
//! .add_dialogue_function(gold);
//! ```
//!
//! A command that means to make the dialogue wait takes the token off
//! [`DialogueCall`] and hands it to whatever will end the wait.
//! See [`call::command`] for that, and [`call::function`] for what a
//! function may return.
//!
//! # Subjects and features
//!
//! `has` asks the game a yes-or-no question without calling anything:
//! `[if Alice has Angry]` is whether the entity named `Alice` holds the
//! component registered as `Angry`. A [`RepliqueSubject`] names the entity,
//! [`register_replique_feature`] names the component:
//!
//! ```rust
//! # use bevy::prelude::*;
//! use bevy_replique::prelude::*;
//!
//! #[derive(Component)]
//! struct Angry;
//!
//! # let mut app = App::new();
//! app.register_replique_feature::<Angry>("Angry");
//!
//! // `[if Alice has Angry]` is now true, until the component is removed.
//! app.world_mut().spawn((RepliqueSubject::new("Alice"), Angry));
//! ```
//!
//! A name the game was never given stops the dialogue with an error instead
//! of answering `false`, so that a typo does not quietly pick the `[else]`.
//! See [`call::features`] for subjects that only some dialogues can see, and
//! for features whose value a dialogue can read, as in `Alice.Mana`.
//!
//! # Modules
//!
//! - [`asset`] loads `.rep` files, and reports parse errors as asset errors.
//! - [`plugin`] is the plugin and its system sets.
//! - [`runner`] is the [`DialogueRunner`] component driving one dialogue.
//! - [`message`] is everything the game and a runner send each other.
//! - [`call`] is commands, functions, the conversion of their arguments, and
//! the subjects and features `has` asks about.
//!
//! # Examples
//!
//! The [`examples`] directory has five runnable ones, from a minimal UI to
//! custom commands, functions, features and several dialogues at once:
//!
//! ```sh
//! cargo run -p bevy_replique --example bevy_simple
//! ```
//!
//! [`examples`]: https://github.com/Galdormin/replique/tree/main/crates/bevy_replique/examples
//! [`DialogueRunner`]: runner::DialogueRunner
//! [`StartDialogue`]: message::StartDialogue
//! [`DialogueLine`]: message::DialogueLine
//! [`DialogueChoices`]: message::DialogueChoices
//! [`DialogueLine::tags`]: message::DialogueLine::tags
//! [`DialogueChoice::tags`]: message::DialogueChoice::tags
//! [`DialogueCommand`]: message::DialogueCommand
//! [`DialogueFinished`]: message::DialogueFinished
//! [`ResumeDialogue`]: message::ResumeDialogue
//! [`ResumeInput::Advance`]: message::ResumeInput::Advance
//! [`ResumeInput::Select`]: message::ResumeInput::Select
//! [`DialogueToken`]: call::DialogueToken
//! [`DialogueCall`]: call::DialogueCall
//! [`RepliquePlugin`]: plugin::RepliquePlugin
//! [`RepliqueSubject`]: call::features::RepliqueSubject
//! [`register_replique_feature`]: call::features::DialogueFeatureAppExt::register_replique_feature