aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Silas Bartha <silas@exvacuum.dev>2024-11-21 12:17:44 -0500
committerLibravatar Silas Bartha <silas@exvacuum.dev>2024-11-21 12:17:44 -0500
commit26e2eddd1efeb0a5fff8ffabebefdae48c4a65dc (patch)
tree7f0903a6c90fb4e09d2894f55cffd0acbcb51350
parent99c398cc127dbc83480f98fea8c76f7c19d4dce8 (diff)
Thu Nov 21 12:17:44 PM EST 2024
-rw-r--r--Cargo.toml27
-rw-r--r--src/actor.rs8
-rw-r--r--src/actor/components.rs52
-rw-r--r--src/actor/events.rs72
-rw-r--r--src/actor/mod.rs99
-rw-r--r--src/actor/resources.rs8
-rw-r--r--src/actor/systems.rs121
-rw-r--r--src/commands.rs52
-rw-r--r--src/conditionals.rs202
-rw-r--r--src/events.rs6
-rw-r--r--src/lib.rs78
-rw-r--r--src/lua_api.rs121
-rw-r--r--src/observers.rs18
-rw-r--r--src/payload.rs84
-rw-r--r--src/payload/components/mod.rs56
-rw-r--r--src/payload/mod.rs27
-rw-r--r--src/resources.rs12
-rw-r--r--src/room_generation/mod.rs0
-rw-r--r--src/systems.rs10
-rw-r--r--src/utils.rs17
-rw-r--r--src/watcher.rs5
-rw-r--r--src/yarnspinner_api.rs79
22 files changed, 955 insertions, 199 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 7390cc8..68f5f11 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,22 +4,25 @@ version = "0.2.1"
edition = "2021"
[dependencies]
-anyhow = "1.0.83"
-async-channel = "2.3.1"
-notify = "6.1.1"
-tar = "0.4.41"
-xz2 = "0.1.7"
-rust-crypto = "0.2.36"
-multi_key_map = "0.3.0"
+anyhow = "1.0"
+async-channel = "2.3"
+notify = "7.0"
+tar = "0.4"
+xz2 = "0.1"
+rust-crypto = "0.2"
+multi_key_map = "0.3"
serde = "1.0"
-rmp-serde = "1.3.0"
+rmp-serde = "1.3"
anymap = "0.12"
-notify-debouncer-full = "0.3"
+notify-debouncer-full = "0.4"
md5 = "0.7"
bevy-async-ecs = "0.6"
aes = "0.8"
hex = "0.4"
hex-literal = "0.4"
+uuid = "1.11"
+strum = "0.26"
+lazy_static = "1.5"
[dependencies.bevy]
version = "0.14"
@@ -42,9 +45,9 @@ features = ["serde"]
version = "0.6"
features = ["lua"]
-[dependencies.strum]
-version = "0.26"
-features = ["derive"]
+[dependencies.bevy_basic_interaction]
+git = "https://github.com/exvacuum/bevy_basic_interaction.git"
+tag = "v0.1.0"
[features]
default = ["yarnspinner"]
diff --git a/src/actor.rs b/src/actor.rs
deleted file mode 100644
index 3198f32..0000000
--- a/src/actor.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use bevy::{prelude::*, utils::HashMap};
-use yarnspinner::{core::LineId, runtime::Dialogue};
-
-#[derive(Component)]
-pub struct Actor {
- pub dialogue: Dialogue,
- pub metadata: HashMap<LineId, Vec<String>>,
-}
diff --git a/src/actor/components.rs b/src/actor/components.rs
new file mode 100644
index 0000000..43987c0
--- /dev/null
+++ b/src/actor/components.rs
@@ -0,0 +1,52 @@
+//! Components related to actors
+
+use bevy::{prelude::*, utils::HashMap};
+use yarnspinner::{compiler::{Compiler, File}, core::{Library, LineId}, runtime::{Dialogue, MemoryVariableStorage, StringTableTextProvider}};
+
+/// Main actor component, holds state about dialogue along with the dialogue runner itself
+#[derive(Component)]
+pub struct Actor {
+ /// Whether this actor is currently conversing
+ pub active: bool,
+ /// Yarnspinner dialogue runner
+ pub dialogue: Dialogue,
+ /// Yarnspinner dialogue metadata
+ pub metadata: HashMap<LineId, Vec<String>>,
+}
+
+impl Actor {
+ /// Create a new actor from the given source code, starting on the given start node, and with
+ /// the given function library
+ pub fn new(file_name: &str, source: &[u8], start_node: &str, function_library: &Library) -> Self {
+ let compilation = Compiler::new()
+ .add_file(File {
+ source: String::from_utf8_lossy(source).into(),
+ file_name: file_name.into(),
+ })
+ .compile()
+ .unwrap();
+
+ let mut base_language_string_table = std::collections::HashMap::new();
+ let mut metadata = HashMap::new();
+
+ for (k, v) in compilation.string_table {
+ base_language_string_table.insert(k.clone(), v.text);
+ metadata.insert(k, v.metadata);
+ }
+
+ let mut text_provider = StringTableTextProvider::new();
+ text_provider.extend_base_language(base_language_string_table);
+
+ let mut dialogue = Dialogue::new(Box::new(MemoryVariableStorage::new()), Box::new(text_provider));
+ dialogue.library_mut().extend(function_library.clone());
+ dialogue.add_program(compilation.program.unwrap());
+ dialogue.set_node(start_node).unwrap();
+
+ Self {
+ active: false,
+ dialogue,
+ metadata,
+ }
+ }
+}
+
diff --git a/src/actor/events.rs b/src/actor/events.rs
new file mode 100644
index 0000000..e24e7f3
--- /dev/null
+++ b/src/actor/events.rs
@@ -0,0 +1,72 @@
+//! Actor-related events
+
+use bevy::prelude::*;
+use yarnspinner::{core::LineId, runtime::{Command, DialogueOption, Line, OptionId}};
+
+/// Event called by user to progress dialogue
+#[derive(Debug, Event)]
+pub enum ContinueDialogueEvent {
+ /// Continue to next line of dialogue for given actor entity
+ Continue(Entity),
+ /// Submit option selection to given actor entity
+ SelectedOption {
+ /// Target actor entity
+ actor: Entity,
+ /// Selected option ID
+ option: OptionId
+ },
+}
+
+/// Event called by plugin in response to a corresponding yarnspinner dialogue events
+///
+/// The user should catch these events to update UI, and never call it directly.
+#[derive(Event)]
+pub enum DialogueEvent {
+ /// Recieved new line of dialogue
+ Line {
+ /// Actor entity
+ actor: Entity,
+ /// Line of dialogue received
+ line: Line,
+ },
+ /// Dialogue complete
+ DialogueComplete {
+ /// Actor entity
+ actor: Entity,
+ },
+ /// Encountered an option selection
+ Options {
+ /// Actor entity
+ actor: Entity,
+ /// Options to select from
+ options: Vec<DialogueOption>,
+ },
+ /// Triggered a yarnspinner command
+ Command {
+ /// Actor entity
+ actor: Entity,
+ /// Triggered command
+ command: Command,
+ },
+ /// Node started
+ NodeStart {
+ /// Actor entity
+ actor: Entity,
+ /// Name of started node
+ name: String,
+ },
+ /// Node complete
+ NodeComplete {
+ /// Actor entity
+ actor: Entity,
+ /// Name of completed node
+ name: String,
+ },
+ /// Received line hints
+ LineHints {
+ /// Actor entity
+ actor: Entity,
+ /// Lines affected
+ lines: Vec<LineId>,
+ },
+}
diff --git a/src/actor/mod.rs b/src/actor/mod.rs
new file mode 100644
index 0000000..9d0af43
--- /dev/null
+++ b/src/actor/mod.rs
@@ -0,0 +1,99 @@
+//! NPCs containing their own individual yarnspinner contexts
+// TODO: Split off into own crate?
+
+use std::sync::{Arc, Mutex};
+
+use bevy::{prelude::*, utils::HashMap};
+use lazy_static::lazy_static;
+use resources::FunctionLibrary;
+use yarnspinner::{core::{LineId, YarnValue}, runtime::Dialogue};
+
+pub mod components;
+pub mod events;
+pub mod resources;
+mod systems;
+
+lazy_static! {
+ /// Custom yarnspinner variable storage
+ /// Stores variables as <instance>.<varname>
+ /// Global variables are stored in the "global" instance
+ pub static ref DIRWORLD_VARIABLE_STORAGE: Arc<Mutex<DirworldVariableStorage>> =
+ Arc::new(Mutex::new(DirworldVariableStorage::default()));
+}
+
+/// Plugin which controls the behavior of actors
+pub struct ActorPlugin {
+ pub custom_function_registration: Option<fn(&mut FunctionLibrary)>,
+}
+
+impl Plugin for ActorPlugin {
+ fn build(&self, app: &mut App) {
+ let mut function_library = FunctionLibrary::default();
+ function_library.add_function("get_string", get_string);
+ function_library.add_function("get_number", get_number);
+ function_library.add_function("get_bool", get_bool);
+ if let Some(custom_function_registration) = &self.custom_function_registration {
+ (custom_function_registration)(&mut function_library)
+ }
+
+ app.add_systems(
+ Update,
+ (systems::handle_dialog_initiation, systems::progress_dialog, systems::handle_variable_set_commands),
+ )
+ .insert_resource(function_library)
+ .add_event::<events::ContinueDialogueEvent>()
+ .add_event::<events::DialogueEvent>();
+ }
+}
+
+fn get_string(instance_name: &str, var_name: &str) -> String {
+ if let Some(YarnValue::String(value)) = DIRWORLD_VARIABLE_STORAGE
+ .lock()
+ .unwrap()
+ .get(instance_name, var_name)
+ {
+ value
+ } else {
+ "".into()
+ }
+}
+
+fn get_number(instance_name: &str, var_name: &str) -> f32 {
+ if let Some(YarnValue::Number(value)) = DIRWORLD_VARIABLE_STORAGE
+ .lock()
+ .unwrap()
+ .get(instance_name, var_name)
+ {
+ value
+ } else {
+ 0.0
+ }
+}
+
+fn get_bool(instance_name: &str, var_name: &str) -> bool {
+ if let Some(YarnValue::Boolean(value)) = DIRWORLD_VARIABLE_STORAGE
+ .lock()
+ .unwrap()
+ .get(instance_name, var_name)
+ {
+ value
+ } else {
+ false
+ }
+}
+
+/// Variable Storage
+#[derive(Default, Debug)]
+pub struct DirworldVariableStorage(pub HashMap<String, YarnValue>);
+
+impl DirworldVariableStorage {
+ /// Set value of instance variable (use "global" for global)
+ pub fn set(&mut self, instance_name: &str, var_name: &str, value: YarnValue) {
+ self.0.insert(format!("{instance_name}.{var_name}"), value);
+ }
+
+ /// Get value of instance variable (use "global" for global)
+ pub fn get(&self, instance_name: &str, var_name: &str) -> Option<YarnValue> {
+ self.0.get(&format!("{instance_name}.{var_name}")).cloned()
+ }
+}
diff --git a/src/actor/resources.rs b/src/actor/resources.rs
new file mode 100644
index 0000000..76ead59
--- /dev/null
+++ b/src/actor/resources.rs
@@ -0,0 +1,8 @@
+//! Actor-related resources
+
+use bevy::prelude::*;
+use yarnspinner::core::Library;
+
+/// Library of yarnspinner function callbacks
+#[derive(Resource, Deref, DerefMut, Default, Debug)]
+pub struct FunctionLibrary(pub Library);
diff --git a/src/actor/systems.rs b/src/actor/systems.rs
new file mode 100644
index 0000000..a719858
--- /dev/null
+++ b/src/actor/systems.rs
@@ -0,0 +1,121 @@
+use bevy::prelude::*;
+use bevy_basic_interaction::events::InteractionEvent;
+use yarnspinner::core::YarnValue;
+
+use super::{
+ components::Actor,
+ events::{ContinueDialogueEvent, DialogueEvent}, DIRWORLD_VARIABLE_STORAGE,
+};
+
+pub fn handle_dialog_initiation(
+ mut event_reader: EventReader<InteractionEvent>,
+ mut actor_query: Query<(Entity, &mut Actor)>,
+ mut event_writer: EventWriter<ContinueDialogueEvent>,
+) {
+ for InteractionEvent { interactable, .. } in event_reader.read() {
+ if let Ok((actor_entity, mut actor)) = actor_query.get_mut(*interactable) {
+ actor.active = true;
+ event_writer.send(ContinueDialogueEvent::Continue(actor_entity));
+ }
+ }
+}
+
+pub fn progress_dialog(
+ mut event_reader: EventReader<ContinueDialogueEvent>,
+ mut actor_query: Query<&mut Actor>,
+ mut event_writer: EventWriter<DialogueEvent>,
+) {
+ for event in event_reader.read() {
+ let actor_entity = match event {
+ ContinueDialogueEvent::Continue(actor) => actor,
+ ContinueDialogueEvent::SelectedOption { actor, .. } => actor,
+ };
+
+ if let Ok(mut actor) = actor_query.get_mut(*actor_entity) {
+ if let ContinueDialogueEvent::SelectedOption { option, .. } = event {
+ actor.dialogue.set_selected_option(*option).unwrap();
+ }
+ if actor.dialogue.current_node().is_none() {
+ actor.dialogue.set_node("Start").unwrap();
+ }
+ match actor.dialogue.continue_() {
+ Ok(events) => {
+ info!("BATCH");
+ for event in events {
+ info!("Event: {:?}", event);
+ match event {
+ yarnspinner::prelude::DialogueEvent::Line(line) => {
+ event_writer.send(DialogueEvent::Line {
+ actor: *actor_entity,
+ line,
+ });
+ }
+ yarnspinner::prelude::DialogueEvent::DialogueComplete => {
+ event_writer.send(DialogueEvent::DialogueComplete {
+ actor: *actor_entity,
+ });
+ }
+ yarnspinner::prelude::DialogueEvent::Options(options) => {
+ event_writer.send(DialogueEvent::Options {
+ actor: *actor_entity,
+ options,
+ });
+ }
+ yarnspinner::runtime::DialogueEvent::Command(command) => {
+ event_writer.send(DialogueEvent::Command {
+ actor: *actor_entity,
+ command,
+ });
+ }
+ yarnspinner::runtime::DialogueEvent::NodeStart(name) => {
+ event_writer.send(DialogueEvent::NodeStart {
+ actor: *actor_entity,
+ name,
+ });
+ }
+ yarnspinner::runtime::DialogueEvent::NodeComplete(name) => {
+ event_writer.send(DialogueEvent::NodeComplete {
+ actor: *actor_entity,
+ name,
+ });
+ }
+ yarnspinner::runtime::DialogueEvent::LineHints(lines) => {
+ event_writer.send(DialogueEvent::LineHints {
+ actor: *actor_entity,
+ lines,
+ });
+ }
+ }
+ }
+ }
+ Err(err) => error!("{:?}", err),
+ }
+ }
+ }
+}
+
+pub fn handle_variable_set_commands(
+ mut event_reader: EventReader<DialogueEvent>,
+ mut event_writer: EventWriter<ContinueDialogueEvent>,
+) {
+ for event in event_reader.read() {
+ if let DialogueEvent::Command { command, actor } = event {
+ if command.name != "set_var" {
+ continue;
+ }
+
+ event_writer.send(ContinueDialogueEvent::Continue(*actor));
+
+ if command.parameters.len() != 3 {
+ warn!("Incorrect number of parameters passed to set command: {}", command.parameters.len());
+ continue;
+ }
+
+ if let YarnValue::String(instance_name) = &command.parameters[0] {
+ if let YarnValue::String(var_name) = &command.parameters[1] {
+ DIRWORLD_VARIABLE_STORAGE.lock().unwrap().set(instance_name, var_name, command.parameters[2].clone());
+ }
+ }
+ }
+ }
+}
diff --git a/src/commands.rs b/src/commands.rs
index 6066ab7..60da108 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -1,11 +1,7 @@
-use std::{
- fs, path::PathBuf,
-};
+use std::{fs, path::PathBuf};
use bevy::{
- ecs::
- world::{Command, CommandQueue}
- ,
+ ecs::world::{Command, CommandQueue},
prelude::*,
tasks::AsyncComputeTaskPool,
};
@@ -18,9 +14,10 @@ use occule::Error;
use xz2::read::{XzDecoder, XzEncoder};
use crate::{
- payload::{DirworldComponent, DirworldComponentDiscriminants, DirworldEntityPayload}, resources::{
- DirworldCodecs, DirworldObservers, DirworldTasks,
- }, utils::extract_entity_payload, Extensions
+ payload::DirworldEntityPayload,
+ resources::{DirworldCodecs, DirworldObservers, DirworldTasks},
+ utils::extract_entity_payload,
+ Extensions,
};
struct DirworldLockDoorCommand {
@@ -57,7 +54,13 @@ impl Command for DirworldLockDoorCommand {
let result = crypter
.encrypt(&mut read_buffer, &mut write_buffer, true)
.expect("Failed to encrypt data!");
- encrypted.extend(write_buffer.take_read_buffer().take_remaining().iter().map(|&i|i));
+ encrypted.extend(
+ write_buffer
+ .take_read_buffer()
+ .take_remaining()
+ .iter()
+ .map(|&i| i),
+ );
match result {
BufferResult::BufferUnderflow => break,
BufferResult::BufferOverflow => {}
@@ -72,11 +75,9 @@ impl Command for DirworldLockDoorCommand {
// Insert key hash as payload relationship
let key_digest = md5::compute(&self.key[..16]);
- let mut payload = payload.unwrap_or_default();
- payload.push(DirworldComponent::Relationship {
- label: "key".into(),
- hash: key_digest.0,
- });
+ let mut payload = payload.unwrap_or_else(|| DirworldEntityPayload::new());
+ let relationships = payload.relationships.get_or_insert_default();
+ relationships.insert("key".into(), key_digest.0);
// Write payload
let mut command_queue = CommandQueue::default();
@@ -119,7 +120,13 @@ impl Command for DirworldUnlockDoorCommand {
let result = decrypter
.decrypt(&mut read_buffer, &mut write_buffer, true)
.expect("Failed to encrypt data!");
- decrypted.extend(write_buffer.take_read_buffer().take_remaining().iter().map(|&i|i));
+ decrypted.extend(
+ write_buffer
+ .take_read_buffer()
+ .take_remaining()
+ .iter()
+ .map(|&i| i),
+ );
match result {
BufferResult::BufferUnderflow => break,
BufferResult::BufferOverflow => {}
@@ -137,16 +144,9 @@ impl Command for DirworldUnlockDoorCommand {
fs::remove_file(path.clone()).unwrap();
if let Some(mut payload) = payload {
- for (index, relationship) in payload.iter().enumerate().filter(|(_, x)| {
- DirworldComponentDiscriminants::from(*x)
- == DirworldComponentDiscriminants::Relationship
- }) {
- if let DirworldComponent::Relationship { label, .. } = relationship {
- if label == "key" {
- payload.remove(index);
- break;
- }
- }
+ // Remove key relationship
+ if let Some(ref mut relationships) = payload.relationships {
+ relationships.remove("key");
}
// Write payload
diff --git a/src/conditionals.rs b/src/conditionals.rs
new file mode 100644
index 0000000..266379a
--- /dev/null
+++ b/src/conditionals.rs
@@ -0,0 +1,202 @@
+use bevy::{
+ ecs::system::SystemState,
+ prelude::{AncestorIter, Entity, Parent, Query, World},
+};
+use serde::{Deserialize, Serialize};
+use strum::{AsRefStr, EnumString};
+use uuid::Uuid;
+
+use crate::{components::DirworldEntity, resources::DirworldCurrentDir};
+
+// I Store Conditions as Enum Data
+#[derive(Serialize, Deserialize, AsRefStr, Debug, Default, Clone, PartialEq, Eq)]
+pub enum Condition {
+ #[default]
+ #[strum(serialize = "Always True")]
+ True,
+ #[strum(serialize = "Child Of")]
+ ChildOf {
+ child: Uuid,
+ parent: Uuid,
+ },
+ #[strum(serialize = "Parent Of")]
+ ParentOf {
+ parent: Uuid,
+ child: Uuid,
+ },
+ #[strum(serialize = "Descendant Of")]
+ DescendantOf {
+ descendant: Uuid,
+ ancestor: Uuid,
+ },
+ #[strum(serialize = "Ancestor Of")]
+ AncestorOf {
+ ancestor: Uuid,
+ descendant: Uuid,
+ },
+ #[strum(serialize = "In Room")]
+ InRoom(Uuid),
+ #[strum(serialize = "Object In Room")]
+ ObjectInRoom(Uuid),
+}
+
+impl Condition {
+ pub fn evaluate(&self, world: &mut World) -> bool {
+ match self {
+ Condition::True => true,
+ Condition::ChildOf { child, parent } => parent_of(world, *parent, *child),
+ Condition::ParentOf { parent, child } => parent_of(world, *parent, *child),
+ Condition::DescendantOf {
+ descendant,
+ ancestor,
+ } => ancestor_of(world, *ancestor, *descendant),
+ Condition::AncestorOf {
+ ancestor,
+ descendant,
+ } => ancestor_of(world, *ancestor, *descendant),
+ Condition::InRoom(uuid) => in_room(world, *uuid),
+ Condition::ObjectInRoom(uuid) => object_in_room(world, *uuid),
+ }
+ }
+
+ pub fn get_api_function_name(&self) -> &'static str {
+ match self {
+ Condition::True => "conditional_true",
+ Condition::ChildOf { .. } => "conditional_child_of",
+ Condition::ParentOf { .. } => "conditional_parent_of",
+ Condition::DescendantOf { .. } => "conditional_descendant_of",
+ Condition::AncestorOf { .. } =>"conditional_ancestor_of",
+ Condition::InRoom(_) => "conditional_in_room",
+ Condition::ObjectInRoom(_) => "conditional_object_in_room",
+ }
+ }
+
+ pub fn from_api_function_name_and_args(name: &str, args: &[&str]) -> Option<Self> {
+ match name {
+ "conditional_true" => Some(Condition::True),
+ "conditional_child_of" => {
+ let Some(child) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ let Some(parent) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ Some(Condition::ChildOf { child, parent })
+ }
+ "conditional_parent_of" => {
+ let Some(child) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ let Some(parent) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ Some(Condition::ParentOf { child, parent })
+ }
+ "conditional_descendant_of" => {
+ let Some(descendant) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ let Some(ancestor) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ Some(Condition::DescendantOf { descendant, ancestor })
+ }
+ "conditional_ancestor_of" => {
+ let Some(descendant) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ let Some(ancestor) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ Some(Condition::AncestorOf { descendant, ancestor })
+ }
+ "condtitional_in_room" => {
+ let Some(room_id) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ Some(Condition::InRoom(room_id))
+ }
+ "condtitional_object_in_room" => {
+ let Some(object_id) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else {
+ return None;
+ };
+ Some(Condition::ObjectInRoom(object_id))
+ }
+ _ => None,
+ }
+ }
+}
+
+// Condition Checkers beyond this point
+
+fn ancestor_of(world: &mut World, ancestor: Uuid, descendant: Uuid) -> bool {
+ let mut system_state =
+ SystemState::<(Query<(Entity, &DirworldEntity)>, Query<&Parent>)>::new(world);
+ let (dirworld_entities, parents) = system_state.get(world);
+ let Some((ancestor_entity, _)) = dirworld_entities.iter().find(|(_, entity)| {
+ entity
+ .payload
+ .as_ref()
+ .is_some_and(|payload| payload.id == ancestor)
+ }) else {
+ return false;
+ };
+
+ let Some((descendant_entity, _)) = dirworld_entities.iter().find(|(_, entity)| {
+ entity
+ .payload
+ .as_ref()
+ .is_some_and(|payload| payload.id == descendant)
+ }) else {
+ return false;
+ };
+
+ AncestorIter::new(&parents, descendant_entity)
+ .find(|descendant| *descendant == ancestor_entity)
+ .is_some()
+}
+
+fn parent_of(world: &mut World, parent: Uuid, child: Uuid) -> bool {
+ let mut system_state =
+ SystemState::<(Query<(Entity, &DirworldEntity)>, Query<&Parent>)>::new(world);
+ let (dirworld_entities, parents) = system_state.get(world);
+ let Some((parent_entity, _)) = dirworld_entities.iter().find(|(_, entity)| {
+ entity
+ .payload
+ .as_ref()
+ .is_some_and(|payload| payload.id == parent)
+ }) else {
+ return false;
+ };
+
+ let Some((child_entity, _)) = dirworld_entities.iter().find(|(_, entity)| {
+ entity
+ .payload
+ .as_ref()
+ .is_some_and(|payload| payload.id == child)
+ }) else {
+ return false;
+ };
+
+ parents
+ .get(child_entity)
+ .is_ok_and(|parent| parent.get() == parent_entity)
+}
+
+fn in_room(world: &mut World, room: Uuid) -> bool {
+ let current_dir = world.resource::<DirworldCurrentDir>();
+ current_dir.payload.as_ref().is_some_and(|payload| payload.id == room)
+}
+
+fn object_in_room(world: &mut World, object: Uuid) -> bool {
+ let mut dirworld_entities = world.query::<&DirworldEntity>();
+ dirworld_entities
+ .iter(world)
+ .find(|entity| {
+ entity
+ .payload
+ .as_ref()
+ .is_some_and(|payload| payload.id == object)
+ })
+ .is_some()
+}
diff --git a/src/events.rs b/src/events.rs
index 4d12f5b..2fa7f81 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -17,13 +17,13 @@ pub enum DirworldNavigationEvent {
},
}
-#[derive(Debug, Event, Deref, DerefMut)]
+#[derive(Debug, Event, Deref, DerefMut, Clone)]
pub struct DirworldLeaveRoom(pub PathBuf);
-#[derive(Debug, Event, Deref, DerefMut)]
+#[derive(Debug, Event, Deref, DerefMut, Clone)]
pub struct DirworldEnterRoom(pub PathBuf);
-#[derive(Debug, Event, Deref, DerefMut)]
+#[derive(Debug, Event, Deref, DerefMut, Clone)]
pub struct DirworldChangeRoot(pub PathBuf);
#[derive(Event)]
diff --git a/src/lib.rs b/src/lib.rs
index cf56ccf..e6a5f25 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,9 +4,14 @@
use std::{ffi::OsStr, path::PathBuf};
+use actor::ActorPlugin;
+use bevy::render::mesh::ExtrusionBuilder;
use bevy::{ecs::system::IntoObserverSystem, prelude::*};
use bevy_scriptum::{runtimes::lua::LuaRuntime, BuildScriptingRuntime, ScriptingRuntimeBuilder};
-use events::{DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom, DirworldNavigationEvent, DirworldSpawn};
+use events::{
+ DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom, DirworldNavigationEvent,
+ DirworldSpawn,
+};
use occule::Codec;
use resources::DirworldCache;
use resources::{
@@ -15,6 +20,7 @@ use resources::{
};
pub use watcher::DirworldWatcherEvent;
pub use watcher::DirworldWatcherSet;
+use yarnspinner::core::Library;
/// Components used by this plugin
pub mod components;
@@ -34,7 +40,7 @@ mod systems;
mod observers;
-mod utils;
+pub mod utils;
/// Payload for dirworld entities
pub mod payload;
@@ -44,6 +50,12 @@ pub mod actor;
mod lua_api;
+pub mod conditionals;
+
+pub mod yarnspinner_api;
+
+pub mod room_generation;
+
/// Plugin which enables high-level interaction
#[derive(Default)]
pub struct DirworldPlugin {
@@ -54,32 +66,42 @@ pub struct DirworldPlugin {
impl Plugin for DirworldPlugin {
fn build(&self, app: &mut App) {
info!("building");
- app.add_systems(Startup, watcher::setup)
- .add_systems(
- Update,
- (systems::remove_completed_tasks, lua_api::trigger_update),
- )
- .add_systems(PostUpdate, (watcher::update, systems::sync_entity_transforms))
- .add_scripting::<LuaRuntime>(|runtime| {
- let runtime = lua_api::register(runtime);
- if let Some(register_custom) = &self.register_custom_lua_api {
- (register_custom)(runtime);
- }
- })
- .init_resource::<DirworldCache>()
- .init_resource::<DirworldRootDir>()
- .init_resource::<DirworldCurrentDir>()
- .init_resource::<DirworldTasks>()
- .init_resource::<DirworldObservers>()
- .init_resource::<DirworldCodecs>()
- .add_event::<DirworldEnterRoom>()
- .add_event::<DirworldLeaveRoom>()
- .add_event::<DirworldChangeRoot>()
- .add_event::<DirworldWatcherEvent>()
- .observe(observers::navigate_to_room)
- .observe(observers::handle_changes)
- .observe(observers::change_root)
- .observe(observers::navigate_from_room);
+ app.add_plugins(ActorPlugin {
+ custom_function_registration: Some(yarnspinner_api::setup_yarnspinner_functions),
+ })
+ .add_systems(Startup, watcher::setup)
+ .add_systems(
+ Update,
+ (
+ systems::remove_completed_tasks,
+ lua_api::trigger_update,
+ yarnspinner_api::process_commands,
+ ),
+ )
+ .add_systems(
+ PostUpdate,
+ watcher::update,
+ )
+ .add_scripting::<LuaRuntime>(|runtime| {
+ let runtime = lua_api::register(runtime);
+ if let Some(register_custom) = &self.register_custom_lua_api {
+ (register_custom)(runtime);
+ }
+ })
+ .init_resource::<DirworldCache>()
+ .init_resource::<DirworldRootDir>()
+ .init_resource::<DirworldCurrentDir>()
+ .init_resource::<DirworldTasks>()
+ .init_resource::<DirworldObservers>()
+ .init_resource::<DirworldCodecs>()
+ .add_event::<DirworldEnterRoom>()
+ .add_event::<DirworldLeaveRoom>()
+ .add_event::<DirworldChangeRoot>()
+ .add_event::<DirworldWatcherEvent>()
+ .observe(observers::navigate_to_room)
+ .observe(observers::handle_changes)
+ .observe(observers::change_root)
+ .observe(observers::navigate_from_room);
}
}
diff --git a/src/lua_api.rs b/src/lua_api.rs
index cda2486..f9a34d4 100644
--- a/src/lua_api.rs
+++ b/src/lua_api.rs
@@ -1,5 +1,13 @@
+use std::str::FromStr;
+
use bevy::prelude::*;
-use bevy_scriptum::{runtimes::lua::{BevyEntity, BevyVec3, LuaRuntime, LuaScriptData}, ScriptingRuntimeBuilder, Runtime};
+use bevy_scriptum::{
+ runtimes::lua::{BevyEntity, BevyVec3, LuaRuntime, LuaScriptData},
+ Runtime, ScriptingRuntimeBuilder,
+};
+use uuid::Uuid;
+
+use crate::{components::DirworldEntity, conditionals::Condition};
pub fn trigger_update(
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
@@ -8,7 +16,7 @@ pub fn trigger_update(
) {
let delta = time.delta_seconds();
for (entity, mut script_data) in scripted_entities.iter_mut() {
- if let Err(e) = scripting_runtime.call_fn("on_update", &mut script_data, entity, (delta, )) {
+ if let Err(e) = scripting_runtime.call_fn("on_update", &mut script_data, entity, (delta,)) {
error!("Encountered lua scripting error: {:?}", e);
}
}
@@ -18,14 +26,28 @@ pub fn trigger_update(
macro_rules! register_fns {
($runtime:expr, $($function:expr),+) => {
- {
+ {
$runtime$(.add_function(stringify!($function).to_string(), $function))+
}
};
}
-pub fn register(runtime: ScriptingRuntimeBuilder<LuaRuntime>) -> ScriptingRuntimeBuilder<LuaRuntime> {
- register_fns!(runtime, translate, rotate)
+pub fn register(
+ runtime: ScriptingRuntimeBuilder<LuaRuntime>,
+) -> ScriptingRuntimeBuilder<LuaRuntime> {
+ register_fns!(
+ runtime,
+ translate,
+ rotate,
+ get_dirworld_id,
+ condition_true,
+ condition_ancestor_of,
+ condition_descendant_of,
+ condition_parent_of,
+ condition_child_of,
+ condition_in_room,
+ condition_object_in_room
+ )
}
fn translate(
@@ -50,4 +72,91 @@ fn rotate(
}
}
-// }}}
+fn get_dirworld_id(In((BevyEntity(entity),)): In<(BevyEntity,)>, dirworld_entity_query: Query<&DirworldEntity>) -> Option<String> {
+ dirworld_entity_query.get(entity).ok().and_then(|entity| entity.payload.as_ref().map(|payload| payload.id.to_string()))
+}
+
+// Conditionals
+fn condition_true(world: &mut World) -> bool {
+ Condition::True.evaluate(world)
+}
+
+fn condition_ancestor_of(
+ In((ancestor, descendant)): In<(String, String)>,
+ world: &mut World,
+) -> bool {
+ let Ok(ancestor) = Uuid::from_str(&ancestor) else {
+ warn!("Provided ancestor is not a valid UUID");
+ return false;
+ };
+ let Ok(descendant) = Uuid::from_str(&descendant) else {
+ warn!("Provided descendant is not a valid UUID");
+ return false;
+ };
+ Condition::AncestorOf {
+ ancestor,
+ descendant,
+ }
+ .evaluate(world)
+}
+
+fn condition_descendant_of(
+ In((descendant, ancestor)): In<(String, String)>,
+ world: &mut World,
+) -> bool {
+ let Ok(ancestor) = Uuid::from_str(&ancestor) else {
+ warn!("Provided ancestor is not a valid UUID");
+ return false;
+ };
+ let Ok(descendant) = Uuid::from_str(&descendant) else {
+ warn!("Provided descendant is not a valid UUID");
+ return false;
+ };
+ Condition::DescendantOf {
+ ancestor,
+ descendant,
+ }
+ .evaluate(world)
+}
+
+fn condition_parent_of(In((parent, child)): In<(String, String)>, world: &mut World) -> bool {
+ let Ok(parent) = Uuid::from_str(&parent) else {
+ warn!("Provided parent is not a valid UUID");
+ return false;
+ };
+ let Ok(child) = Uuid::from_str(&child) else {
+ warn!("Provided child is not a valid UUID");
+ return false;
+ };
+ Condition::ParentOf { parent, child }.evaluate(world)
+}
+
+fn condition_child_of(In((child, parent)): In<(String, String)>, world: &mut World) -> bool {
+ let Ok(parent) = Uuid::from_str(&parent) else {
+ warn!("Provided parent is not a valid UUID");
+ return false;
+ };
+ let Ok(child) = Uuid::from_str(&child) else {
+ warn!("Provided child is not a valid UUID");
+ return false;
+ };
+ Condition::ChildOf { parent, child }.evaluate(world)
+}
+
+fn condition_in_room(In((room,)): In<(String,)>, world: &mut World) -> bool {
+ let Ok(room) = Uuid::from_str(&room) else {
+ warn!("Provided room is not a valid UUID");
+ return false;
+ };
+ Condition::InRoom(room).evaluate(world)
+}
+
+fn condition_object_in_room(In((object,)): In<(String,)>, world: &mut World) -> bool {
+ let Ok(object) = Uuid::from_str(&object) else {
+ warn!("Provided object is not a valid UUID");
+ return false;
+ };
+ Condition::ObjectInRoom(object).evaluate(world)
+}
+
+// }}}
diff --git a/src/observers.rs b/src/observers.rs
index b321c53..44edb50 100644
--- a/src/observers.rs
+++ b/src/observers.rs
@@ -12,16 +12,17 @@ use crate::{
resources::{
DirworldCache, DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir,
},
- utils::{despawn_entity_by_path, spawn_entity},
+ utils::{despawn_entity_by_path, extract_entity_payload, spawn_entity},
DirworldWatcherEvent,
};
/// On navigation from a room, insert modified payloads into the cache
pub fn navigate_from_room(
- _: Trigger<DirworldLeaveRoom>,
+ trigger: Trigger<DirworldLeaveRoom>,
entities: Query<(Entity, Ref<DirworldEntity>), Without<Persist>>,
mut cache: ResMut<DirworldCache>,
mut commands: Commands,
+ mut event_writer: EventWriter<DirworldLeaveRoom>,
) {
for (entity, dirworld_entity) in entities.iter() {
if let Some(payload) = &dirworld_entity.payload {
@@ -30,6 +31,7 @@ pub fn navigate_from_room(
}
commands.entity(entity).despawn_recursive();
}
+ event_writer.send(trigger.event().clone());
}
pub fn navigate_to_room(
@@ -39,9 +41,16 @@ pub fn navigate_to_room(
observers: Res<DirworldObservers>,
codecs: Res<DirworldCodecs>,
mut commands: Commands,
+ mut event_writer: EventWriter<DirworldEnterRoom>,
+ mut current_dir: ResMut<DirworldCurrentDir>,
) {
let path = &trigger.event().0;
+ let room_payload = extract_entity_payload(&path.join(".door"), &codecs).0;
+ *current_dir = DirworldCurrentDir {
+ path: path.to_path_buf(),
+ payload: room_payload,
+ };
let entries = match path.read_dir() {
Ok(entries) => entries
.flatten()
@@ -74,6 +83,7 @@ pub fn navigate_to_room(
for entry in entries {
spawn_entity(&entry, &mut cache, &codecs, &observers, &mut commands);
}
+ event_writer.send(trigger.event().clone());
}
pub fn handle_changes(
@@ -83,6 +93,7 @@ pub fn handle_changes(
observers: Res<DirworldObservers>,
codecs: Res<DirworldCodecs>,
mut cache: ResMut<DirworldCache>,
+ mut event_writer: EventWriter<DirworldWatcherEvent>,
) {
let event = &trigger.event().0;
info!("Watcher Event: {event:?}");
@@ -121,12 +132,12 @@ pub fn handle_changes(
// warn!("Not Processed.")
}
}
+ event_writer.send(trigger.event().clone());
}
pub fn change_root(
trigger: Trigger<DirworldChangeRoot>,
mut root_dir: ResMut<DirworldRootDir>,
- mut current_dir: ResMut<DirworldCurrentDir>,
mut commands: Commands,
) {
if let DirworldRootDir(Some(old_dir)) = root_dir.deref() {
@@ -136,7 +147,6 @@ pub fn change_root(
let new_root = &trigger.event().0;
info!("Changing Root to {}", new_root.display());
**root_dir = Some(new_root.to_path_buf());
- **current_dir = Some(new_root.to_path_buf());
commands.trigger(DirworldEnterRoom(new_root.to_path_buf()));
}
diff --git a/src/payload.rs b/src/payload.rs
deleted file mode 100644
index 2aa3f23..0000000
--- a/src/payload.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use std::{collections::HashMap, str::FromStr};
-
-use avian3d::prelude::RigidBody;
-use bevy::prelude::*;
-use serde::{Deserialize, Serialize};
-use strum::{EnumDiscriminants, EnumString};
-use yarnspinner::core::YarnValue;
-
-#[derive(Serialize, Deserialize, Default, Clone, Deref, DerefMut, Debug)]
-pub struct DirworldEntityPayload(Vec<DirworldComponent>);
-
-impl DirworldEntityPayload {
- pub fn component(&self, name: &str) -> Option<&DirworldComponent> {
- if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) {
- self.iter()
- .find(|component| discriminant == DirworldComponentDiscriminants::from(*component))
- } else {
- None
- }
- }
-
- pub fn component_mut(&mut self, name: &str) -> Option<&mut DirworldComponent> {
- if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) {
- self.iter_mut()
- .find(|component| discriminant == DirworldComponentDiscriminants::from(&**component))
- } else {
- None
- }
- }
-
- pub fn components(&self, name: &str) -> Vec<&DirworldComponent> {
- if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) {
- self.iter()
- .filter(|component| {
- discriminant == DirworldComponentDiscriminants::from(*component)
- })
- .collect()
- } else {
- vec![]
- }
- }
-
- pub fn components_mut(&mut self, name: &str) -> Vec<&mut DirworldComponent> {
- if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) {
- self.iter_mut()
- .filter(|component| {
- discriminant == DirworldComponentDiscriminants::from(&**component)
- })
- .collect()
- } else {
- vec![]
- }
- }
-}
-
-#[derive(Serialize, Deserialize, Clone, EnumDiscriminants, Debug)]
-#[strum_discriminants(derive(EnumString))]
-pub enum DirworldComponent {
- Transform(Transform),
- Name(String),
- Actor {
- local_variables: HashMap<String, YarnValue>,
- yarn_source: Vec<u8>,
- },
- Voice {
- pitch: i32,
- preset: i32,
- bank: i32,
- variance: u32,
- speed: f32,
- },
- Rigidbody(RigidBody),
- MeshCollider {
- convex: bool,
- sensor: bool,
- },
- Script {
- lua_source: Vec<u8>,
- },
- Relationship {
- label: String,
- hash: [u8; 16],
- },
-}
diff --git a/src/payload/components/mod.rs b/src/payload/components/mod.rs
new file mode 100644
index 0000000..e8919fd
--- /dev/null
+++ b/src/payload/components/mod.rs
@@ -0,0 +1,56 @@
+use std::collections::HashMap;
+
+use avian3d::prelude::RigidBody;
+use bevy::prelude::*;
+use serde::{Deserialize, Serialize};
+use yarnspinner::core::YarnValue;
+
+#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
+pub struct Transform(pub bevy::prelude::Transform);
+
+#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
+pub struct Name(pub String);
+
+#[derive(Serialize, Deserialize, Clone, Default, Debug)]
+pub struct Actor {
+ pub local_variables: HashMap<String, YarnValue>,
+ pub yarn_source: Vec<u8>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Voice {
+ pub pitch: i32,
+ pub preset: i32,
+ pub bank: i32,
+ pub variance: u32,
+ pub speed: f32,
+}
+
+impl Default for Voice {
+ fn default() -> Self {
+ Self {
+ pitch: 60,
+ preset: 0,
+ bank: 0,
+ variance: 3,
+ speed: 1.0,
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
+pub struct Rigidbody(pub RigidBody);
+
+#[derive(Serialize, Deserialize, Clone, Default, Debug)]
+pub struct MeshCollider {
+ pub convex: bool,
+ pub sensor: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone, Default, Debug)]
+pub struct Script {
+ pub lua_source: Vec<u8>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
+pub struct Relationships(pub HashMap<String, [u8; 16]>);
diff --git a/src/payload/mod.rs b/src/payload/mod.rs
new file mode 100644
index 0000000..483fee5
--- /dev/null
+++ b/src/payload/mod.rs
@@ -0,0 +1,27 @@
+use serde::{Deserialize, Serialize};
+use uuid::Uuid;
+
+pub mod components;
+
+#[derive(Serialize, Deserialize, Default, Clone, Debug)]
+pub struct DirworldEntityPayload {
+ pub id: Uuid,
+ pub transform: components::Transform,
+ pub name: Option<components::Name>,
+ pub actor: Option<components::Actor>,
+ pub voice: Option<components::Voice>,
+ pub rigidbody: Option<components::Rigidbody>,
+ pub mesh_collider: Option<components::MeshCollider>,
+ pub scripts: Option<Vec<components::Script>>,
+ pub relationships: Option<components::Relationships>,
+}
+
+impl DirworldEntityPayload {
+ pub fn new() -> Self {
+ Self {
+ id: Uuid::new_v4(),
+ ..Default::default()
+ }
+ }
+}
+
diff --git a/src/resources.rs b/src/resources.rs
index 152a149..cbf570d 100644
--- a/src/resources.rs
+++ b/src/resources.rs
@@ -3,6 +3,7 @@ use std::{collections::{BTreeMap, HashMap}, path::PathBuf};
use bevy::{ecs::world::CommandQueue, prelude::*, tasks::Task};
use multi_key_map::MultiKeyMap;
use occule::Codec;
+use uuid::Uuid;
use crate::payload::DirworldEntityPayload;
@@ -11,18 +12,21 @@ use crate::payload::DirworldEntityPayload;
pub struct DirworldRootDir(pub Option<PathBuf>);
/// Current directory of the world
-#[derive(Resource, Deref, DerefMut, Default)]
-pub struct DirworldCurrentDir(pub Option<PathBuf>);
+#[derive(Resource, Default)]
+pub struct DirworldCurrentDir{
+ pub path: PathBuf,
+ pub payload: Option<DirworldEntityPayload>,
+}
/// Running background tasks
#[derive(Default, Resource, Deref, DerefMut)]
pub struct DirworldTasks(pub BTreeMap<String, Task<Option<CommandQueue>>>);
#[derive(Debug, Default, Resource, Deref, DerefMut)]
-pub(crate) struct DirworldObservers(pub MultiKeyMap<EntryType, Entity>);
+pub struct DirworldObservers(pub MultiKeyMap<EntryType, Entity>);
#[derive(Default, Resource, Deref, DerefMut)]
-pub(crate) struct DirworldCodecs(pub MultiKeyMap<String, Box<dyn Codec + Send + Sync>>);
+pub struct DirworldCodecs(pub MultiKeyMap<String, Box<dyn Codec + Send + Sync>>);
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum EntryType {
diff --git a/src/room_generation/mod.rs b/src/room_generation/mod.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/room_generation/mod.rs
diff --git a/src/systems.rs b/src/systems.rs
index 6c3bc71..3f894ec 100644
--- a/src/systems.rs
+++ b/src/systems.rs
@@ -3,7 +3,7 @@ use bevy::{
tasks::{block_on, futures_lite::future},
};
-use crate::{components::DirworldEntity, payload::DirworldComponent, resources::DirworldTasks};
+use crate::{components::DirworldEntity, resources::DirworldTasks};
pub fn remove_completed_tasks(mut commands: Commands, mut tasks: ResMut<DirworldTasks>) {
tasks.retain(|_, task| {
@@ -22,12 +22,8 @@ pub fn sync_entity_transforms(
for (mut dirworld_entity, transform, global_transform) in dirworld_entity_query.iter_mut() {
if transform.is_changed() && !transform.is_added() {
if let Some(payload) = &mut dirworld_entity.payload {
- if let Some(DirworldComponent::Transform(payload_transform)) =
- payload.component_mut("Transform")
- {
- let transform = global_transform.compute_transform();
- *payload_transform = transform;
- }
+ let transform = global_transform.compute_transform();
+ *payload.transform = transform;
}
}
}
diff --git a/src/utils.rs b/src/utils.rs
index df0a8b6..e4692bb 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -3,7 +3,7 @@ use std::{fs, path::PathBuf};
use bevy::prelude::*;
use crate::{
- components::DirworldEntity, events::DirworldSpawn, payload::{DirworldComponent, DirworldEntityPayload}, resources::{DirworldCache, DirworldCodecs, DirworldObservers, EntryType}, Extensions
+ components::DirworldEntity, events::DirworldSpawn, payload::DirworldEntityPayload, resources::{DirworldCache, DirworldCodecs, DirworldObservers, EntryType}, Extensions
};
pub fn extract_entity_payload(
@@ -74,18 +74,7 @@ pub fn spawn_entity(
payload = Some(cached_payload);
}
- let transform = if let Some(component) = payload
- .as_ref()
- .and_then(|payload| payload.component("Transform"))
- {
- if let DirworldComponent::Transform(transform) = component {
- transform.clone()
- } else {
- panic!("BAD DECOMPOSE: TRANSFORM ({component:?})");
- }
- } else {
- Transform::default()
- };
+ let transform = payload.as_ref().map(|payload| payload.transform.clone()).unwrap_or_default();
let entry_type = if entry.is_dir() {
EntryType::Folder
} else {
@@ -94,7 +83,7 @@ pub fn spawn_entity(
let entity = commands
.spawn((
SpatialBundle {
- transform,
+ transform: *transform,
..Default::default()
},
DirworldEntity {
diff --git a/src/watcher.rs b/src/watcher.rs
index 78d74f2..8918dda 100644
--- a/src/watcher.rs
+++ b/src/watcher.rs
@@ -20,7 +20,7 @@ use crate::{
pub struct DirworldWatcherSet;
/// Event fired when a file watcher event is caught.
-#[derive(Event, Debug)]
+#[derive(Event, Clone, Debug)]
pub struct DirworldWatcherEvent(pub notify::Event);
#[derive(Resource)]
@@ -65,10 +65,9 @@ async fn file_watcher(rx: Receiver<PathBuf>, tx: Sender<notify::Event>) {
loop {
while let Ok(message) = rx.try_recv() {
if let Some(old_path) = &old_path {
- debouncer.watcher().unwatch(old_path).unwrap();
+ debouncer.unwatch(old_path).unwrap();
}
debouncer
- .watcher()
.watch(&message, RecursiveMode::NonRecursive)
.unwrap();
old_path = Some(message);
diff --git a/src/yarnspinner_api.rs b/src/yarnspinner_api.rs
new file mode 100644
index 0000000..e7f8781
--- /dev/null
+++ b/src/yarnspinner_api.rs
@@ -0,0 +1,79 @@
+use bevy::{log::info, prelude::World};
+use lazy_static::lazy_static;
+use std::sync::{
+ Arc, Condvar, Mutex,
+};
+
+use crate::{actor::resources::FunctionLibrary, conditionals::Condition};
+
+lazy_static! {
+ static ref YARNSPINNER_WORLD: Arc<Mutex<World>> = Arc::new(Mutex::new(World::new()));
+ static ref YARNSPINNER_CVAR: Arc<(Condvar, Mutex<bool>)> =
+ Arc::new((Condvar::new(), Mutex::new(false)));
+ static ref YARNSPINNER_COMMAND_COUNT: Arc<(Condvar, Mutex<usize>)> =
+ Arc::new((Condvar::new(), Mutex::new(0)));
+}
+
+macro_rules! register_fns {
+ ($runtime:expr, $($function:expr),+) => {
+ {
+ $runtime$(.add_function(stringify!($function).to_string(), $function))+
+ }
+ };
+}
+
+pub fn setup_yarnspinner_functions(library: &mut FunctionLibrary) {
+ register_fns!(library, conditional_true);
+}
+
+pub fn process_commands(world: &mut World) {
+ let command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap();
+ if *command_count <= 0 {
+ return;
+ }
+
+ info!("Swapping World to Yarnspinner");
+ let mut temp_world = World::new();
+ std::mem::swap(&mut temp_world, world);
+ {
+ let mut world_swapped = YARNSPINNER_CVAR.1.lock().unwrap();
+ *world_swapped = true;
+ }
+
+ let mut command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap();
+ while !*command_count <= 0 {
+ info!("Command Count: {}", *command_count);
+ command_count = YARNSPINNER_COMMAND_COUNT.0.wait(command_count).unwrap();
+ }
+
+ info!("Swapping World from Yarnspinner");
+ std::mem::swap(&mut temp_world, world);
+ {
+ let mut world_swapped = YARNSPINNER_CVAR.1.lock().unwrap();
+ *world_swapped = false;
+ }
+}
+
+fn conditional(condition: Condition) -> bool {
+ {
+ let mut command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap();
+ *command_count += 1;
+ }
+
+ let mut world_swapped = YARNSPINNER_CVAR.1.lock().unwrap();
+ while !*world_swapped {
+ world_swapped = YARNSPINNER_CVAR.0.wait(world_swapped).unwrap();
+ }
+
+ let result = condition.evaluate(&mut YARNSPINNER_WORLD.lock().unwrap());
+
+ {
+ let mut command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap();
+ *command_count -= 1;
+ }
+ result
+}
+
+fn conditional_true() -> bool {
+ conditional(Condition::True)
+}