bevy_entitiles 0.3.0

A 2d tilemap library for bevy. With many useful algorithms/tools built in.
use bevy::{
    asset::AssetServer,
    ecs::{entity::Entity, system::{Commands, EntityCommands}},
    hierarchy::DespawnRecursiveExt,
    math::{IVec2, UVec2, Vec2, Vec4},
    prelude::SpatialBundle,
    sprite::SpriteBundle,
    transform::components::Transform,
    utils::HashMap,
};

use crate::{
    math::aabb::IAabb2d,
    serializing::pattern::TilemapPattern,
    tilemap::{
        buffers::TileBuffer,
        bundles::TilemapBundle,
        map::{
            TileRenderSize, TilemapLayerOpacities, TilemapName, TilemapSlotSize, TilemapStorage,
            TilemapTexture, TilemapTransform, TilemapType,
        },
        tile::{TileBuilder, TileLayer, TileTexture},
    },
    DEFAULT_CHUNK_SIZE,
};

use super::{
    components::{LayerIid, LdtkLoadedLevel, LevelIid, EntityIid, LdtkTempTransform},
    traits::{LdtkEntityRegistry, LdtkEntityTagRegistry},
    json::{level::{LayerInstance, Level, TileInstance, EntityInstance}, field::FieldInstance},
    resources::{LdtkAssets, LdtkLevelManager, LdtkPatterns},
    LdtkLoaderMode,
};

#[cfg(feature = "algorithm")]
pub mod path;
#[cfg(feature = "physics")]
pub mod physics;

#[derive(Debug, Clone)]
pub struct PackedLdtkEntity {
    pub instance: EntityInstance,
    pub fields: HashMap<String, FieldInstance>,
    pub iid: EntityIid,
    pub transform: LdtkTempTransform,
}

impl PackedLdtkEntity {
    pub fn instantiate(
        self,
        commands: &mut EntityCommands,
        entity_registry: &LdtkEntityRegistry,
        entity_tag_registry: &LdtkEntityTagRegistry,
        manager: &LdtkLevelManager,
        ldtk_assets: &LdtkAssets,
        asset_server: &AssetServer,
    ) {
        let phantom_entity = {
            if let Some(e) = entity_registry.get(&self.instance.identifier) {
                e
            } else if !manager.ignore_unregistered_entities {
                panic!(
                    "Could not find entity type with entity identifier: {}! \
                    You need to register it using App::register_ldtk_entity::<T>() first!",
                    self.instance.identifier
                );
            } else {
                return;
            }
        };
        
        self.instance.tags.iter().for_each(|tag| {
            if let Some(entity_tag) = entity_tag_registry.get(tag) {
                entity_tag.add_tag(commands);
            } else if !manager.ignore_unregistered_entity_tags {
                panic!(
                    "Could not find entity tag with tag: {}! \
                    You need to register it using App::register_ldtk_entity_tag::<T>() first! \
                    Or call LdtkLevelManager::ignore_unregistered_entity_tags to ignore.",
                    tag
                );
            }
        });
        
        phantom_entity.spawn(
            commands,
            &self.instance,
            &self.fields,
            asset_server,
            &manager,
            ldtk_assets,
        )
    }
}

pub type LayerOpacity = f32;

pub struct LdtkLayers<'a> {
    pub ty: LdtkLoaderMode,
    pub level_entity: Entity,
    pub layers: Vec<Option<(TilemapPattern, TilemapTexture, LayerIid, LayerOpacity)>>,
    pub entities: Vec<PackedLdtkEntity>,
    pub tilesets: &'a HashMap<i32, TilemapTexture>,
    pub translation: Vec2,
    pub base_z_index: i32,
    pub background: SpriteBundle,
    #[cfg(feature = "algorithm")]
    pub path_layer: Option<(
        path::LdtkPathLayer,
        HashMap<IVec2, crate::tilemap::algorithm::path::PathTile>,
    )>,
    #[cfg(feature = "physics")]
    pub physics_layer: Option<(physics::LdtkPhysicsLayer, Vec<i32>, UVec2)>,
}

impl<'a> LdtkLayers<'a> {
    pub fn new(
        level_entity: Entity,
        total_layers: usize,
        ldtk_assets: &'a LdtkAssets,
        translation: Vec2,
        base_z_index: i32,
        ty: LdtkLoaderMode,
        background: SpriteBundle,
    ) -> Self {
        Self {
            level_entity,
            layers: vec![None; total_layers],
            entities: vec![],
            tilesets: &ldtk_assets.tilesets,
            translation,
            base_z_index,
            background,
            ty,
            #[cfg(feature = "algorithm")]
            path_layer: None,
            #[cfg(feature = "physics")]
            physics_layer: None,
        }
    }

    pub fn set_tile(&mut self, layer_index: usize, layer: &LayerInstance, tile: &TileInstance) {
        self.try_create_new_layer(layer_index, layer);

        let (pattern, texture, _, _) = self.layers[layer_index].as_mut().unwrap();
        let tile_size = texture.desc.tile_size;
        let tile_index = IVec2 {
            x: tile.px[0] / tile_size.x as i32,
            y: -tile.px[1] / tile_size.y as i32,
        };

        if let Some(ser_tile) = pattern.tiles.get_mut(tile_index) {
            let TileTexture::Static(tile_layers) = &mut ser_tile.texture else {
                unreachable!()
            };
            tile_layers.push(TileLayer::new().with_texture_index(tile.tile_id as u32));
        } else {
            let builder = TileBuilder::new()
                .with_layer(
                    0,
                    TileLayer::new()
                        .with_texture_index(tile.tile_id as u32)
                        .with_flip_raw(tile.flip as u32),
                )
                .with_color(Vec4::new(1., 1., 1., tile.alpha));
            pattern.tiles.tiles.insert(tile_index, builder);
        }
    }

    pub fn set_entity(&mut self, entity: PackedLdtkEntity) {
        self.entities.push(entity);
    }

    fn try_create_new_layer(&mut self, layer_index: usize, layer: &LayerInstance) {
        let tileset = self
            .tilesets
            .get(&layer.tileset_def_uid.unwrap())
            .cloned()
            .unwrap();

        if self.layers[layer_index].is_some() {
            return;
        }

        let aabb = IAabb2d {
            min: IVec2::new(0, -layer.c_hei + 1),
            max: IVec2::new(layer.c_wid - 1, 0),
        };

        self.layers[layer_index] = Some((
            TilemapPattern {
                label: Some(layer.identifier.clone()),
                tiles: TileBuffer {
                    aabb,
                    tiles: HashMap::new(),
                },
                #[cfg(feature = "algorithm")]
                path_tiles: TileBuffer {
                    aabb,
                    tiles: HashMap::new(),
                },
                #[cfg(feature = "physics")]
                physics_tiles: TileBuffer {
                    aabb,
                    tiles: HashMap::new(),
                },
            },
            tileset,
            LayerIid(layer.iid.clone()),
            layer.opacity,
        ));
    }

    pub fn apply_all(
        &mut self,
        commands: &mut Commands,
        ldtk_patterns: &mut LdtkPatterns,
        level: &Level,
        entity_registry: &LdtkEntityRegistry,
        entity_tag_registry: &LdtkEntityTagRegistry,
        manager: &LdtkLevelManager,
        ldtk_assets: &LdtkAssets,
        asset_server: &AssetServer,
    ) {
        match self.ty {
            LdtkLoaderMode::Tilemap => {
                let mut layers = HashMap::with_capacity(self.layers.len());
                let mut entities = HashMap::with_capacity(self.entities.len());

                self.entities.drain(..).for_each(|entity| {
                    let mut ldtk_entity = commands.spawn((
                        entity.transform.clone(),
                        entity.iid.clone()
                    ));
                    entities.insert(entity.iid.clone(), ldtk_entity.id());
                    entity.instantiate(
                        &mut ldtk_entity,
                        entity_registry,
                        entity_tag_registry,
                        manager,
                        ldtk_assets,
                        asset_server,
                    );
                });

                self.layers
                .drain(..)
                .enumerate()
                .filter_map(|(i,e)| {
                    if let Some(e) = e {
                        Some((i,e))
                    }else {
                        None
                    }
                })
                    .for_each(|(index, (pattern, texture, iid, opacity))| {
                        let tilemap_entity = commands.spawn_empty().id();
                        let mut tilemap = TilemapBundle {
                            name: TilemapName(pattern.label.clone().unwrap()),
                            ty: TilemapType::Square,
                            tile_render_size: TileRenderSize(texture.desc.tile_size.as_vec2()),
                            slot_size: TilemapSlotSize(texture.desc.tile_size.as_vec2()),
                            texture: texture.clone(),
                            storage: TilemapStorage::new(DEFAULT_CHUNK_SIZE, tilemap_entity),
                            tilemap_transform: TilemapTransform {
                                translation: self.translation,
                                z_index: self.base_z_index - index as i32 - 1,
                                ..Default::default()
                            },
                            layer_opacities: TilemapLayerOpacities([opacity; 4].into()),
                            ..Default::default()
                        };

                        tilemap
                            .storage
                            .fill_with_buffer(commands, IVec2::NEG_Y, pattern.tiles);

                        #[cfg(feature = "algorithm")]
                        if let Some((path_layer, path_tilemap)) = &self.path_layer {
                            if path_layer.parent == tilemap.name.0 {
                                commands.entity(tilemap_entity).insert(
                                    crate::tilemap::algorithm::path::PathTilemap {
                                        storage:
                                            crate::tilemap::chunking::storage::ChunkedStorage::from_mapper(
                                                path_tilemap.clone(),
                                                None,
                                            ),
                                    },
                                );
                            }
                        }

                        #[cfg(feature = "physics")]
                        if let Some((physics_layer,physics_data,size)) = &self.physics_layer {
                            if pattern.label.clone().unwrap() == physics_layer.parent { 
                                commands.entity(tilemap_entity).insert(crate::tilemap::physics::DataPhysicsTilemap::new(
                                    IVec2::new(0, -(size.y as i32)),
                                    physics_data.clone(),
                                    *size,
                                    physics_layer.air,
                                    physics_layer.tiles.clone().unwrap_or_default()
                                ));
                            }
                        }

                        commands.entity(tilemap_entity).insert((tilemap, iid.clone()));
                        layers.insert(iid, tilemap_entity);
                    });

                let bg = commands.spawn(self.background.clone()).id();

                commands.entity(self.level_entity).insert((
                    LdtkLoadedLevel {
                        identifier: level.identifier.clone(),
                        layers,
                        entities,
                        background: bg,
                    },
                    SpatialBundle {
                        transform: Transform::from_translation(self.translation.extend(0.)),
                        ..Default::default()
                    },
                    LevelIid(level.iid.clone()),
                ));
            }
            LdtkLoaderMode::MapPattern => {
                let level_pack = self
                    .layers
                    .drain(..)
                    .filter_map(|p| {
                        #[allow(unused_mut)]
                        if let Some(mut p) = p {
                            #[cfg(feature = "algorithm")]
                            if let Some((path_layer, path_tiles)) = &self.path_layer {
                                if path_layer.parent == p.0.label.clone().unwrap() {
                                    p.0.path_tiles.tiles = path_tiles.clone();
                                }
                            }

                            Some((p.0, p.1))
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>();

                #[cfg(feature = "physics")]
                if let Some((physics_layer, physics_data, size)) = self.physics_layer.as_ref() {
                    ldtk_patterns.physics_parent = physics_layer.parent.clone();
                    ldtk_patterns.physics_patterns.insert(
                        level.identifier.clone(),
                        crate::tilemap::physics::DataPhysicsTilemap::new(
                            IVec2::ZERO,
                            physics_data.clone(),
                            *size,
                            physics_layer.air,
                            physics_layer.tiles.clone().unwrap_or_default(),
                        ),
                    );
                }

                ldtk_patterns.insert(
                    level.identifier.clone(),
                    level_pack,
                    self.background.clone(),
                );
                commands.entity(self.level_entity).despawn_recursive();
            }
        }
    }

    #[cfg(feature = "algorithm")]
    pub fn assign_path_layer(
        &mut self,
        path: path::LdtkPathLayer,
        tilemap: HashMap<IVec2, crate::tilemap::algorithm::path::PathTile>,
    ) {
        self.path_layer = Some((path, tilemap));
    }

    #[cfg(feature = "physics")]
    pub fn assign_physics_layer(
        &mut self,
        physics_layer: physics::LdtkPhysicsLayer,
        physics_data: Vec<i32>,
        size: UVec2,
    ) {
        self.physics_layer = Some((physics_layer, physics_data, size));
    }
}