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
//! Partial implementation of the *Ink* markup language for game dialogue.
//!
//! Ink is a creation of [Inkle](https://www.inklestudios.com/). For more information
//! about the language, [see their website](https://www.inklestudios.com/ink/).
//!
//! # Why `inkling`?
//! *   Simple interface for walking through branching stories or dialog trees
//! *   Designed to slot into an external framework: like Inkle's implementation this
//!     is not a stand alone game engine, just a processor that will feed the story
//!     text and choices to the user
//! *   Rust native, no wrestling with Unity or C# integration
//! *   Support for non-latin alphabets in identifiers
//! *   Few dependencies: currently only `serde` as an optional dependency
//!     to de/serialize stories, probably `rand` as maybe-optional in the future.
//!
//! # Why not `inkling`?
//! *   Fewer features than Inkle's implementation of the language
//! *   Untested in serious work loads and large scripts
//! *   Not even alpha status, what is this???
//!
//! # Usage example
//!
//! ## A short story
//! This example shows the basics of using `inkling`.
//!
//! ### Parsing the story
//! ```
//! use inkling::read_story_from_string;
//!
//! // Imagine that this is a complete `Ink` story!
//!
//! let story_content = "\
//! Hello, World!
//! *   Hello[ back!] right back at you!
//! ";
//!
//! let mut story = read_story_from_string(story_content).unwrap();
//! ```
//!
//! ### Starting the story processor
//! ```
//! # use inkling::read_story_from_string;
//! # let story_content = "Hello, World!\n*Hello[ back!] right back at you!";
//! # let mut story = read_story_from_string(story_content).unwrap();
//! // We will supply a buffer for the story to read content into
//! let mut line_buffer = Vec::new();
//!
//! // Mark the story as being prepared by calling `start`
//! story.start().unwrap();
//!
//! // Begin the story processing: it will return once it encounters the branching choice
//! let result = story.resume(&mut line_buffer).unwrap();
//!
//! assert_eq!(&line_buffer[0].text, "Hello, World!\n");
//! ```
//!
//!
//! ### Accessing the content of a presented choice
//! ```
//! # use inkling::read_story_from_string;
//! # let story_content = "Hello, World!\n*Hello[ back!] right back at you!";
//! # let mut story = read_story_from_string(story_content).unwrap();
//! # let mut line_buffer = Vec::new();
//! # story.start().unwrap();
//! # let result = story.resume(&mut line_buffer).unwrap();
//! use inkling::Prompt;
//!
//! // The story provided us with the choice in the result
//! let choice = match result {
//!     Prompt::Choice(choices) => choices[0].clone(),
//!     _ => unreachable!(),
//! };
//!
//! assert_eq!(&choice.text, "Hello back!");
//! ```
//!
//! ### Resuming with a selected choice
//! ```
//! # use inkling::{read_story_from_string, Prompt};
//! # let story_content = "Hello, World!\n*Hello[ back!] right back at you!";
//! # let mut story = read_story_from_string(story_content).unwrap();
//! # let mut line_buffer = Vec::new();
//! # story.start().unwrap();
//! # let result = story.resume(&mut line_buffer).unwrap();
//! // Resume by supplying a selected choice index and calling `resume`
//! story.make_choice(0).unwrap();
//!
//! match story.resume(&mut line_buffer).unwrap() {
//!     Prompt::Done => (),
//!     _ => unreachable!(),
//! }
//!
//! // The choice text has now been added to the buffer
//! assert_eq!(&line_buffer[1].text, "Hello right back at you!\n");
//! ```
//!
//! ## Story loop
//! The idea is that story processing should be as simple as this loop:
//!
//! ```
//! # use inkling::{read_story_from_string, Prompt};
//! # let mut story = read_story_from_string("Line").unwrap();
//! # let mut line_buffer = Vec::new();
//! # story.start().unwrap();
//! while let Ok(Prompt::Choice(choices)) = story.resume(&mut line_buffer) {
//!     // Present story text to user, then have them select a choice
//!     # break;
//! }
//! ```
//!
//! An example of a simple command line story player is provided in the examples.
//! Run `cargo run --example player` to try it out and browse the source code to see the
//! implementation.
//!
//! # Features
//! A complete recreation of all features in the Ink language is beyond the scope
//! of this project (and my capabilities as a programmer).
//!
//! Currently the processor supports:
//!
//! *   Structure:  Knots, stitches, nested branching choices, gathers, diverts,
//!                 tags for knots and story
//! *   Choices:    Non-sticky, sticky, fallback, line variations, conditions
//! *   Lines:      Plain text, diverts, tags, conditions, alternative sequences (all
//!                 except shuffle)
//! *   Conditions: Nested, `and`/`or` linking, can check against variables
//! *   Reading:    Address validation for diverts and conditions
//! *   Variables:  Used as text in sentences and in conditions, can modify from calling program
//!
//! Hopefully coming:
//!
//! *   Variables:  Modify in the Ink script
//! *   Reading:    Include statements in files
//! *   Validation: Checking conditions for incompatible variables, namespace collisions
//!                 between knots, stitches and variables
//!
//! Unlikely features:
//!
//! *   Structure:  Threads and tunnels (maybe?)
//! *   Program:    Defining functions in the Ink story file, "advanced state tracking,"
//!                 calling Rust functions from the script to get variables
//! *   Mathematics: I'm not yet sure that I want to write a calculator
//!
//! # De/serializing stories
//! Enable the `serde_support` feature to derive `Deserialize` and `Serialize` for all
//! required objects. If you are unfamiliar with `serde`, this corresponds to reading
//! and writing finished story files in their current state. In game terms: saving
//! and loading.
//!
//! For more information about `serde` see their [website](https://serde.rs/).
//!
//! # Contributions
//! I am a complete novice at designing frameworks which will fit into larger schemes.
//! As such I have no real idea of best practices for interacting with an engine like this.
//! If you have a suggestion for how to make it easier for a user to run the processor
//! I would be very glad to hear it!
//!
//! Likewise, contributions are welcome. Please open an issue on
//! [Github](https://github.com/pjohansson/inkling) to discuss improvements or submit
//! a pull request.

mod consts;
pub mod error;
mod follow;
mod knot;
mod line;
mod node;
mod process;
mod story;
mod utils;

pub use error::InklingError;
pub use line::Variable;
pub use story::{
    copy_lines_into_string, read_story_from_string, Choice, Line, LineBuffer, Prompt, Story,
};