use bevy::{ asset::{AssetLoader, LoadContext, io::Reader}, prelude::*, reflect::TypePath, }; use serde::Deserialize; use thiserror::Error; #[derive(Asset, TypePath, Debug, Deserialize, Default, Clone)] pub(crate) struct Monologue { value: Vec>, } impl Monologue { pub fn get(&self, idx: usize) -> Option<&Vec> { self.value.get(idx) } } #[derive(Default)] struct MonologueLoader; #[derive(Debug, Error)] enum MonologueLoaderError { #[error("Could not load asset: {0}")] Io(#[from] std::io::Error), #[error("Could not parse utf8")] Utf8(#[from] std::string::FromUtf8Error), } impl AssetLoader for MonologueLoader { type Asset = Monologue; type Settings = (); type Error = MonologueLoaderError; async fn load( &self, reader: &mut dyn Reader, _settings: &(), _load_context: &mut LoadContext<'_>, ) -> Result { let mut bytes: Vec = Vec::new(); reader.read_to_end(&mut bytes).await?; let raw_string = String::from_utf8(bytes)?; let value: Vec> = raw_string // First split on the '---' separators between batches .split_terminator("---") .map(|batch| { batch // Then split batches into newline-separated groups of text .split_terminator("\n\n") .filter_map(|line| { // Filter out comments, empty lines, and extraneous newlines (!line.starts_with("#") && !line.is_empty() && line != "\n") // Trim the resulting dialog option .then_some(line.trim().into()) }) .collect() }) .filter(|sub: &Vec| !sub.is_empty()) .collect(); info!("Monologue: {:#?}", value); let thing = Monologue { value }; Ok(thing) } fn extensions(&self) -> &[&str] { &["mono"] } } pub struct MonologueAssetsPlugin; impl Plugin for MonologueAssetsPlugin { fn build(&self, app: &mut App) { app.init_asset::() .init_asset_loader::(); } }