micro_quest 0.1.0

Structures and systems for managing game dialog & quests
Documentation
use bevy::app::{App, Plugin};
use bevy::asset::{
	AddAsset, AssetLoader, AssetServer, Assets, BoxedFuture, Error, LoadContext, LoadedAsset,
};
use bevy::ecs::system::SystemParam;
use bevy::prelude::Res;
use serde::Deserialize;

use crate::quest_log::QuestLocator;
use crate::{Quest, QuestLog};

#[derive(Deserialize)]
struct QuestFile {
	quests: Vec<Quest>,
}

pub struct QuestFileLoader;
impl AssetLoader for QuestFileLoader {
	fn load<'a>(
		&'a self,
		bytes: &'a [u8],
		load_context: &'a mut LoadContext,
	) -> BoxedFuture<'a, anyhow::Result<(), Error>> {
		Box::pin(async move {
			let bytes_as_str = String::from_utf8(bytes.to_vec())?;
			let value: QuestFile =
				toml::from_str(&bytes_as_str).map_err(|e| Error::msg(format!("{}", e)))?;

			for quest in value.quests {
				let id = quest.id.clone();
				load_context.set_labeled_asset(id.as_str(), LoadedAsset::new(quest));
			}

			Ok(())
		})
	}

	fn extensions(&self) -> &[&str] {
		&["quest"]
	}
}

pub struct MicroQuestPlugin;
impl Plugin for MicroQuestPlugin {
	fn build(&self, app: &mut App) {
		app.add_asset::<Quest>()
			.add_asset_loader(QuestFileLoader)
			.init_resource::<QuestLog>();
	}
}

/// A basic quest locator that will use the asset server to retrieve quest information
///
/// Quests can either be loaded directly (one path = one quest), or the can be loaded as
/// sub-assets (one path = multiple quests). This locator attempts to support both -
/// if it is asked to locate a single quest asset, it will append `.quest` to the ID to
/// look up the file. If it is asked to locate a sub asset (The path includes a `#` followed
/// by the sub asset name), then it will try to split the id and append `.quest` to what it
/// presumes will be the file name portion, and then recombine to create the full asset + sub
/// asset path.
///
/// e.g.
///
/// - `locator.get_quest("quests/my_quest")` Will look up the file `quests/my_quest.quest`
/// - `locator.get_quest("quests/my_quest_list#MyFirstQuest")` Will look up the file
/// `quests/my_quest_list.quest`, and try to get a handle for the sub resource labelled with `MyFirstQuest`
#[derive(SystemParam)]
pub struct AssetServerQuestLocator<'w> {
	pub assets: Res<'w, AssetServer>,
	pub quests: Res<'w, Assets<Quest>>,
}

impl<'w> QuestLocator for AssetServerQuestLocator<'w> {
	fn get_quest(&self, id: impl ToString) -> Option<&Quest> {
		let base_id = id.to_string();
		let mut parts = base_id.split('#');

		let root = parts.next();
		let inner_id = parts.next();

		root.map(|root| {
			let name = if let Some(inner_id) = inner_id {
				format!("{}.quest#{}", root, inner_id)
			} else {
				format!("{}.quest", root)
			};

			self.assets.load(name)
		})
		.and_then(|handle| self.quests.get(&handle))
	}
}