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}