bevy_yoleck/
level_index.rs

1use std::collections::BTreeSet;
2use std::ops::Deref;
3
4use bevy::asset::AssetLoader;
5use bevy::prelude::*;
6use bevy::reflect::TypePath;
7
8use serde::{Deserialize, Serialize};
9
10use crate::errors::YoleckAssetLoaderError;
11
12/// Describes a level in the index.
13#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct YoleckLevelIndexEntry {
15    /// The name of the file containing the level, relative to where the levels index file is.
16    pub filename: String,
17}
18
19/// An asset loaded from a `.yoli` file (usually `index.yoli`) representing the game's levels.
20///
21/// ```no_run
22/// # use bevy::prelude::*;
23/// # use bevy_yoleck::prelude::*;
24/// fn load_level_system(
25///     mut level_index_handle: Local<Option<Handle<YoleckLevelIndex>>>,
26///     asset_server: Res<AssetServer>,
27///     level_index_assets: Res<Assets<YoleckLevelIndex>>,
28///     mut commands: Commands,
29/// ) {
30///     # let level_number: usize = todo!();
31///     // Keep the handle in local resource, so that Bevy will not unload the level index asset
32///     // between frames.
33///     let level_index_handle = level_index_handle
34///         .get_or_insert_with(|| asset_server.load("levels/index.yoli"))
35///         .clone();
36///     let Some(level_index) = level_index_assets.get(&level_index_handle) else {
37///         // During the first invocation of this system, the level index asset is not going to be
38///         // loaded just yet. Since this system is going to run on every frame during the Loading
39///         // state, it just has to keep trying until it starts in a frame where it is loaded.
40///         return;
41///     };
42///     let level_to_load = level_index[level_number];
43///     let level_handle: Handle<YoleckRawLevel> = asset_server.load(&format!("levels/{}", level_to_load.filename));
44///     commands.spawn(YoleckLoadLevel(level_handle));
45/// }
46/// ```
47#[derive(Asset, TypePath, Debug, Serialize, Deserialize)]
48pub struct YoleckLevelIndex(YoleckLevelIndexHeader, Vec<YoleckLevelIndexEntry>);
49
50/// Internal Yoleck metadata for the levels index.
51#[derive(Debug, Serialize, Deserialize)]
52pub struct YoleckLevelIndexHeader {
53    format_version: usize,
54}
55
56impl YoleckLevelIndex {
57    pub fn new(entries: impl IntoIterator<Item = YoleckLevelIndexEntry>) -> Self {
58        Self(
59            YoleckLevelIndexHeader { format_version: 1 },
60            entries.into_iter().collect(),
61        )
62    }
63}
64
65impl Deref for YoleckLevelIndex {
66    type Target = [YoleckLevelIndexEntry];
67
68    fn deref(&self) -> &Self::Target {
69        &self.1
70    }
71}
72
73pub(crate) struct YoleckLevelIndexLoader;
74
75impl AssetLoader for YoleckLevelIndexLoader {
76    type Asset = YoleckLevelIndex;
77    type Settings = ();
78    type Error = YoleckAssetLoaderError;
79
80    async fn load(
81        &self,
82        reader: &mut dyn bevy::asset::io::Reader,
83        _settings: &Self::Settings,
84        _load_context: &mut bevy::asset::LoadContext<'_>,
85    ) -> Result<Self::Asset, Self::Error> {
86        let mut bytes = Vec::new();
87        reader.read_to_end(&mut bytes).await?;
88        let json = std::str::from_utf8(&bytes)?;
89        let level_index: YoleckLevelIndex = serde_json::from_str(json)?;
90        Ok(level_index)
91    }
92
93    fn extensions(&self) -> &[&str] {
94        &["yoli"]
95    }
96}
97
98/// Accessible only to edit systems - provides information about available levels.
99#[derive(Resource)]
100pub struct YoleckEditableLevels {
101    pub(crate) levels: BTreeSet<String>,
102}
103
104impl YoleckEditableLevels {
105    /// The names of the level files (relative to the levels directory, not the assets directory)
106    pub fn names(&self) -> impl Iterator<Item = &str> {
107        self.levels.iter().map(|l| l.as_str())
108    }
109}