konnektoren_bevy/assets/
level_asset.rs

1use bevy::{
2    asset::{io::Reader, AssetLoader, LoadContext},
3    prelude::*,
4    reflect::TypePath,
5};
6
7#[cfg(feature = "assets")]
8use konnektoren_core::game::GamePath;
9#[cfg(feature = "assets")]
10use serde_yaml;
11#[cfg(feature = "assets")]
12use thiserror::Error;
13
14/// Asset representation of a game level
15#[derive(Asset, TypePath, Debug, Clone)]
16pub struct LevelAsset {
17    #[cfg(feature = "assets")]
18    pub game_path: GamePath,
19    #[cfg(not(feature = "assets"))]
20    pub game_path: String, // Fallback
21    pub file_path: String,
22}
23
24#[cfg(feature = "assets")]
25impl LevelAsset {
26    /// Get the level ID
27    pub fn id(&self) -> &str {
28        &self.game_path.id
29    }
30
31    /// Get the level name
32    pub fn name(&self) -> &str {
33        &self.game_path.name
34    }
35
36    /// Get all challenge IDs referenced in this level
37    pub fn get_challenge_ids(&self) -> Vec<String> {
38        self.game_path
39            .challenges
40            .iter()
41            .map(|c| c.challenge.clone())
42            .collect()
43    }
44}
45
46#[cfg(not(feature = "assets"))]
47impl LevelAsset {
48    /// Get the level ID (fallback)
49    pub fn id(&self) -> &str {
50        &self.game_path
51    }
52
53    /// Get the level name (fallback)
54    pub fn name(&self) -> &str {
55        &self.game_path
56    }
57
58    /// Get challenge IDs (fallback)
59    pub fn get_challenge_ids(&self) -> Vec<String> {
60        vec![]
61    }
62}
63
64/// Loader for level files in YAML format
65#[derive(Default)]
66pub struct LevelAssetLoader;
67
68/// Possible errors that can be produced by LevelAssetLoader
69#[non_exhaustive]
70#[derive(Debug, Error)]
71#[cfg(feature = "assets")]
72pub enum LevelAssetLoaderError {
73    /// An IO Error
74    #[error("Could not load level asset: {0}")]
75    Io(#[from] std::io::Error),
76
77    /// A YAML parsing error
78    #[error("Could not parse YAML level: {0}")]
79    YamlError(#[from] serde_yaml::Error),
80}
81
82#[cfg(not(feature = "assets"))]
83#[derive(Debug)]
84pub enum LevelAssetLoaderError {
85    Io(std::io::Error),
86    YamlError(String),
87}
88
89#[cfg(not(feature = "assets"))]
90impl std::fmt::Display for LevelAssetLoaderError {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        match self {
93            LevelAssetLoaderError::Io(e) => write!(f, "IO Error: {}", e),
94            LevelAssetLoaderError::YamlError(e) => write!(f, "YAML Error: {}", e),
95        }
96    }
97}
98
99#[cfg(not(feature = "assets"))]
100impl std::error::Error for LevelAssetLoaderError {}
101
102#[cfg(feature = "assets")]
103impl AssetLoader for LevelAssetLoader {
104    type Asset = LevelAsset;
105    type Settings = ();
106    type Error = LevelAssetLoaderError;
107
108    async fn load(
109        &self,
110        reader: &mut dyn Reader,
111        _settings: &(),
112        load_context: &mut LoadContext<'_>,
113    ) -> Result<Self::Asset, Self::Error> {
114        let mut bytes = Vec::new();
115        reader.read_to_end(&mut bytes).await?;
116
117        let game_path = serde_yaml::from_slice::<GamePath>(&bytes)?;
118        let file_path = load_context.path().to_string_lossy().to_string();
119
120        info!(
121            "Loaded level '{}' ({}) with {} challenges from {}",
122            game_path.name,
123            game_path.id,
124            game_path.challenges.len(),
125            file_path
126        );
127
128        Ok(LevelAsset {
129            game_path,
130            file_path,
131        })
132    }
133
134    fn extensions(&self) -> &[&str] {
135        &["level.yml", "level.yaml"]
136    }
137}
138
139#[cfg(not(feature = "assets"))]
140impl AssetLoader for LevelAssetLoader {
141    type Asset = LevelAsset;
142    type Settings = ();
143    type Error = LevelAssetLoaderError;
144
145    async fn load(
146        &self,
147        reader: &mut dyn Reader,
148        _settings: &(),
149        load_context: &mut LoadContext<'_>,
150    ) -> Result<Self::Asset, Self::Error> {
151        let mut bytes = Vec::new();
152        reader
153            .read_to_end(&mut bytes)
154            .await
155            .map_err(LevelAssetLoaderError::Io)?;
156
157        let file_path = load_context.path().to_string_lossy().to_string();
158        let game_path = "unknown".to_string(); // Fallback
159
160        Ok(LevelAsset {
161            game_path,
162            file_path,
163        })
164    }
165
166    fn extensions(&self) -> &[&str] {
167        &["level.yml", "level.yaml"]
168    }
169}