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
use std::collections::BTreeSet;
use std::ops::Deref;
use bevy::asset::{AssetLoader, AsyncReadExt};
use bevy::prelude::*;
use bevy::reflect::TypePath;
use serde::{Deserialize, Serialize};
use crate::errors::YoleckAssetLoaderError;
/// Describes a level in the index.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct YoleckLevelIndexEntry {
/// The name of the file containing the level, relative to where the levels index file is.
pub filename: String,
}
/// An asset loaded from a `.yoli` file (usually `index.yoli`) representing the game's levels.
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_yoleck::prelude::*;
/// fn load_level_system(
/// mut level_index_handle: Local<Option<Handle<YoleckLevelIndex>>>,
/// asset_server: Res<AssetServer>,
/// level_index_assets: Res<Assets<YoleckLevelIndex>>,
/// mut commands: Commands,
/// ) {
/// # let level_number: usize = todo!();
/// // Keep the handle in local resource, so that Bevy will not unload the level index asset
/// // between frames.
/// let level_index_handle = level_index_handle
/// .get_or_insert_with(|| asset_server.load("levels/index.yoli"))
/// .clone();
/// let Some(level_index) = level_index_assets.get(&level_index_handle) else {
/// // During the first invocation of this system, the level index asset is not going to be
/// // loaded just yet. Since this system is going to run on every frame during the Loading
/// // state, it just has to keep trying until it starts in a frame where it is loaded.
/// return;
/// };
/// let level_to_load = level_index[level_number];
/// let level_handle: Handle<YoleckRawLevel> = asset_server.load(&format!("levels/{}", level_to_load.filename));
/// commands.spawn(YoleckLoadLevel(level_handle));
/// }
/// ```
#[derive(Asset, TypePath, Debug, Serialize, Deserialize)]
pub struct YoleckLevelIndex(YoleckLevelIndexHeader, Vec<YoleckLevelIndexEntry>);
/// Internal Yoleck metadata for the levels index.
#[derive(Debug, Serialize, Deserialize)]
pub struct YoleckLevelIndexHeader {
format_version: usize,
}
impl YoleckLevelIndex {
pub fn new(entries: impl IntoIterator<Item = YoleckLevelIndexEntry>) -> Self {
Self(
YoleckLevelIndexHeader { format_version: 1 },
entries.into_iter().collect(),
)
}
}
impl Deref for YoleckLevelIndex {
type Target = [YoleckLevelIndexEntry];
fn deref(&self) -> &Self::Target {
&self.1
}
}
pub(crate) struct YoleckLevelIndexLoader;
impl AssetLoader for YoleckLevelIndexLoader {
type Asset = YoleckLevelIndex;
type Settings = ();
type Error = YoleckAssetLoaderError;
async fn load<'a>(
&'a self,
reader: &'a mut bevy::asset::io::Reader<'_>,
_settings: &'a Self::Settings,
_load_context: &'a mut bevy::asset::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let json = std::str::from_utf8(&bytes)?;
let level_index: YoleckLevelIndex = serde_json::from_str(json)?;
Ok(level_index)
}
fn extensions(&self) -> &[&str] {
&["yoli"]
}
}
/// Accessible only to edit systems - provides information about available levels.
#[derive(Resource)]
pub struct YoleckEditableLevels {
pub(crate) levels: BTreeSet<String>,
}
impl YoleckEditableLevels {
/// The names of the level files (relative to the levels directory, not the assets directory)
pub fn names(&self) -> impl Iterator<Item = &str> {
self.levels.iter().map(|l| l.as_str())
}
}