use crate::{
prelude::*,
tiled::{cache::TiledResourceCache, reader::BytesResourceReader},
};
use bevy::{
asset::{io::Reader, AssetLoader, AssetPath, LoadContext},
prelude::*,
};
#[derive(Debug, thiserror::Error)]
pub enum TiledWorldLoaderError {
#[error("Could not load Tiled file: {0}")]
Io(#[from] std::io::Error),
#[error("No map found in this world")]
EmptyWorld,
#[error("Infinite map found in this world (not supported)")]
WorldWithInfiniteMap,
}
#[derive(TypePath)]
pub(crate) struct TiledWorldLoader {
cache: TiledResourceCache,
}
impl FromWorld for TiledWorldLoader {
fn from_world(world: &mut World) -> Self {
Self {
cache: world.resource::<TiledResourceCache>().clone(),
}
}
}
impl AssetLoader for TiledWorldLoader {
type Asset = TiledWorldAsset;
type Settings = ();
type Error = TiledWorldLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
debug!("Start loading world '{}'", load_context.path());
let world_path = load_context.path().path().to_path_buf();
let world = {
let mut loader = tiled::Loader::with_cache_and_reader(
self.cache.clone(),
BytesResourceReader::new(&bytes, load_context),
);
loader
.load_world(&world_path)
.map_err(|e| std::io::Error::other(format!("Could not load Tiled world: {e}")))?
};
if world.maps.is_empty() {
return Err(TiledWorldLoaderError::EmptyWorld);
}
let mut world_rect = Rect::new(0.0, 0.0, 0.0, 0.0);
for map in world.maps.iter() {
let (Some(map_width), Some(map_height)) = (map.width, map.height) else {
return Err(TiledWorldLoaderError::WorldWithInfiniteMap);
};
let map_rect = Rect::new(
map.x as f32,
map.y as f32, map.x as f32 + map_width as f32,
map.y as f32 + map_height as f32,
);
world_rect = world_rect.union(map_rect);
}
let mut maps = Vec::new();
for map in world.maps.iter() {
let map_path = world_path.parent().unwrap().join(map.filename.clone());
let (Some(map_width), Some(map_height)) = (map.width, map.height) else {
return Err(TiledWorldLoaderError::WorldWithInfiniteMap);
};
maps.push((
Rect::new(
map.x as f32,
world_rect.max.y - map_height as f32 - map.y as f32, map.x as f32 + map_width as f32,
world_rect.max.y - map.y as f32,
),
load_context.load(AssetPath::from(map_path)),
));
}
trace!(?maps, "maps");
let world = TiledWorldAsset {
world,
rect: world_rect,
maps,
};
debug!("Loaded world '{}': {:?}", load_context.path(), world);
Ok(world)
}
fn extensions(&self) -> &[&str] {
static EXTENSIONS: &[&str] = &["world"];
EXTENSIONS
}
}
pub(crate) fn plugin(app: &mut App) {
app.init_asset_loader::<TiledWorldLoader>();
}