use bevy::prelude::*;
use bevy::utils::Uuid;
use std::collections::VecDeque;
use crate::prelude::{Actor, ActorSlug, TalkData};
use crate::{JoinNode, LeaveNode, TextNode};
pub mod build_command;
pub mod commands;
#[derive(Default)]
pub struct TalkBuilder {
pub(crate) queue: VecDeque<BuildNode>,
pub(crate) actors: Vec<Actor>,
pub(crate) connect_parent: Option<BuildNodeId>,
}
pub type BuildNodeId = String;
#[derive(Default)]
pub(crate) struct BuildNode {
pub(crate) id: BuildNodeId,
pub(crate) choices: Vec<(String, TalkBuilder)>,
pub(crate) manual_connections: Vec<BuildNodeId>,
pub(crate) actors: Vec<ActorSlug>,
pub(crate) components: Vec<Box<dyn Reflect>>,
}
impl TalkBuilder {
pub fn fill_with_talk_data(self, talk: &TalkData) -> Self {
talk.fill_builder(self)
}
pub fn say(mut self, text: impl Into<String>) -> Self {
let talk_node = BuildNode {
id: Uuid::new_v4().to_string(),
components: vec![Box::new(TextNode(text.into()))],
..default()
};
self.queue.push_back(talk_node);
self
}
pub fn choose(mut self, choices: Vec<(impl Into<String>, Self)>) -> Self {
assert!(!choices.is_empty(), "You can't choose node without choices");
let choices = choices
.into_iter()
.map(|(t, b)| (t.into(), b))
.collect::<Vec<(String, TalkBuilder)>>();
let choice_node = BuildNode {
id: Uuid::new_v4().to_string(),
choices,
..default()
};
self.queue.push_back(choice_node);
self
}
pub fn join(mut self, actor_slugs: &[ActorSlug]) -> Self {
let join_node = BuildNode {
id: Uuid::new_v4().to_string(),
actors: actor_slugs.to_vec(),
components: vec![Box::new(JoinNode)],
..default()
};
self.queue.push_back(join_node);
self
}
pub fn leave(mut self, actor_slugs: &[ActorSlug]) -> Self {
let leave_node = BuildNode {
id: Uuid::new_v4().to_string(),
actors: actor_slugs.to_vec(),
components: vec![Box::new(LeaveNode)],
..default()
};
self.queue.push_back(leave_node);
self
}
pub fn connect_to(mut self, node_id: BuildNodeId) -> Self {
match self.queue.back_mut() {
None => self.connect_parent = Some(node_id),
Some(node) => node.manual_connections.push(node_id),
};
self
}
pub fn last_node_id(&self) -> BuildNodeId {
match self.queue.back() {
None => panic!("You can't get the last node id of an empty builder"),
Some(node) => node.id.clone(),
}
}
pub fn add_actor(mut self, actor: Actor) -> Self {
self.actors.push(actor);
self
}
pub fn add_actors(mut self, actors: Vec<Actor>) -> Self {
self.actors.extend(actors);
self
}
pub fn actor_say(mut self, actor_slug: impl Into<String>, text: impl Into<String>) -> Self {
let talk_node = BuildNode {
id: Uuid::new_v4().to_string(),
actors: vec![actor_slug.into()],
components: vec![Box::new(TextNode(text.into()))],
..default()
};
self.queue.push_back(talk_node);
self
}
pub fn actors_say(mut self, actor_slugs: &[ActorSlug], text: impl Into<String>) -> Self {
let talk_node = BuildNode {
id: Uuid::new_v4().to_string(),
components: vec![Box::new(TextNode(text.into()))],
actors: actor_slugs.to_vec(),
..default()
};
self.queue.push_back(talk_node);
self
}
pub fn empty_node(mut self) -> Self {
let talk_node = BuildNode {
id: Uuid::new_v4().to_string(),
..default()
};
self.queue.push_back(talk_node);
self
}
pub fn with_component<C: Component + Reflect>(mut self, comp: C) -> Self {
match self.queue.back_mut() {
None => panic!("You can't add a custom component to an empty builder"),
Some(node) => node.components.push(Box::new(comp)),
};
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::{fixture, rstest};
#[fixture]
fn talk_builder() -> TalkBuilder {
TalkBuilder::default()
}
#[rstest]
#[case(vec!["Hello"])]
#[case(vec!["Hello", "World!"])]
fn say_pushes_back_nodes(mut talk_builder: TalkBuilder, #[case] expected_texts: Vec<&str>) {
for t in expected_texts.iter() {
talk_builder = talk_builder.say(*t);
}
assert_eq!(talk_builder.queue.len(), expected_texts.len());
assert_eq!(talk_builder.queue.pop_front().unwrap().components.len(), 1);
if expected_texts.len() > 1 {
assert_eq!(talk_builder.queue.pop_front().unwrap().components.len(), 1);
}
}
#[rstest]
fn choose_adds_a_choice_node(talk_builder: TalkBuilder) {
let added_node = talk_builder
.choose(vec![(
"Hello".to_string(),
TalkBuilder::default().say("hello"),
)])
.queue
.pop_front()
.unwrap();
assert_eq!(added_node.choices.len(), 1);
}
#[rstest]
fn connect_to_adds_entry_to_last_node(talk_builder: TalkBuilder) {
let mut builder = talk_builder.say("hello");
let hello_id = builder.last_node_id();
builder = builder.say("how are you?").connect_to(hello_id);
assert_eq!(builder.queue.len(), 2);
let previous_node = builder.queue.pop_back().unwrap();
assert_eq!(previous_node.manual_connections.len(), 1);
}
#[rstest]
fn connect_to_in_empty_builder_sets_connect_parent(talk_builder: TalkBuilder) {
let id = "some id".to_string();
let builder = talk_builder.connect_to(id.clone());
assert_eq!(builder.connect_parent, Some(id));
}
#[test]
#[should_panic]
fn last_node_id_panics_on_empty() {
TalkBuilder::default().last_node_id();
}
#[rstest]
fn test_last_node_id(talk_builder: TalkBuilder) {
let builder = talk_builder.say("hello");
let id = builder.last_node_id();
assert_eq!(id, builder.queue[0].id);
}
#[rstest]
fn test_join(talk_builder: TalkBuilder) {
let actors = vec!["actor1".to_string(), "actor2".to_string()];
let builder = talk_builder.join(&actors);
assert_eq!(builder.queue.len(), 1);
assert_eq!(builder.queue[0].components.len(), 1);
}
#[rstest]
fn test_leave(talk_builder: TalkBuilder) {
let actors = vec!["actor1".to_string(), "actor2".to_string()];
let builder = talk_builder.leave(&actors);
assert_eq!(builder.queue.len(), 1);
assert_eq!(builder.queue[0].components.len(), 1);
}
#[rstest]
fn test_add_actor(talk_builder: TalkBuilder) {
let actor = Actor {
slug: "slug".to_string(),
name: "Actor".to_string(),
};
let builder = talk_builder.add_actor(actor.clone());
assert_eq!(builder.actors.len(), 1);
assert_eq!(builder.actors[0], actor);
}
#[rstest]
fn test_actor_say_success(talk_builder: TalkBuilder) {
let builder = talk_builder.add_actor(Actor {
slug: "slug".to_string(),
name: "Actor".to_string(),
});
let builder = builder.actor_say("slug", "hello");
assert_eq!(builder.queue.len(), 1);
assert_eq!(builder.queue[0].actors[0], "slug");
}
#[derive(Component, Reflect)]
struct MyComp;
#[rstest]
fn add_component_on_last_node(talk_builder: TalkBuilder) {
let builder = talk_builder.say("hello").with_component(MyComp);
assert_eq!(builder.queue.len(), 1);
assert_eq!(builder.queue[0].components.len(), 2);
}
#[rstest]
#[should_panic]
fn add_component_on_empty_panics(talk_builder: TalkBuilder) {
talk_builder.with_component(MyComp);
}
}