nano9 0.1.0-alpha.3

A Pico-8 compatibility layer for Bevy
Documentation
use super::*;
use bevy::utils::hashbrown::hash_map::DefaultHashBuilder;
use std::hash::{BuildHasher, Hash, Hasher};
use bevy_ecs_tilemap::prelude::*;

pub(crate) fn plugin(app: &mut App) {
    #[cfg(feature = "scripting")]
    lua::plugin(app);
}
impl super::Pico8<'_, '_> {
    fn sprite_map(&self, map_index: Option<usize>) -> Result<&SpriteMap, Error> {
        let index = map_index.unwrap_or(0);
        self.pico8_asset()?
            .maps
            .get(index)
            .ok_or(Error::NoSuch(format!("map index {index}").into()))
    }

    fn sprite_map_mut(&mut self, map_index: Option<usize>) -> Result<&mut SpriteMap, Error> {
        let index = map_index.unwrap_or(0);
        self.pico8_asset_mut()?
            .maps
            .get_mut(index)
            .ok_or(Error::NoSuch(format!("map index {index}").into()))
    }

    pub fn map(
        &mut self,
        map_pos: UVec2,
        mut screen_start: Vec2,
        size: UVec2,
        mask: Option<u8>,
        map_index: Option<usize>,
    ) -> Result<Entity, Error> {
        trace!("map");
        let map_index = map_index.unwrap_or(0);
        screen_start = self.state.draw_state.apply_camera_delta(screen_start);
        if cfg!(feature = "negate-y") {
            screen_start.y = -screen_start.y;
        }
        let hash = {
            let mut hasher = DefaultHashBuilder::default().build_hasher();
            "map".hash(&mut hasher);
            map_pos.hash(&mut hasher);
            self.state.palette.hash(&mut hasher);
            self.state.pal_map.hash(&mut hasher);
            size.hash(&mut hasher);
            mask.inspect(|m| m.hash(&mut hasher));
            map_index.hash(&mut hasher);
            hasher.finish()
        };
        let rect = URect { min: map_pos,
                           max: map_pos + size };
        self.state.draw_state.mark_drawn();
        // See if there's already an entity available.
        if let Some(id) = self.resurrect(hash, screen_start) {
            // trace!("Resurrect map with hash {hash}");
            return Ok(id);
        }
        // match self.sprite_map(map_index)?.clone() {
        //     SpriteMap::P8(map) => {
                // trace!("Create map with hash {hash}");

        let map_size = TilemapSize::from(size);
        let clearable = Clearable::new(self.defaults.time_to_live).with_hash(hash);
        let tile_storage = TileStorage::empty(map_size);
        let gfx_material = self.gfx_material().clone();
        let sprite_sheet = self.pico8_asset()?.sprite_sheets.get(self.state.sprite_sheet_index)
                                                       .ok_or(Error::NoSuch("Sprite sheet for map".into()))?;

                let map_entity = self.commands
                    .spawn((
                        Name::new("map"),
                        Transform::from_translation(screen_start.extend(clearable.suggest_z())),
                        Visibility::Inherited,
                        clearable,
                        P8SpriteMap {
                            map_index,
                            sprite_sheet: sprite_sheet.clone(),
                            gfx_material,
                            rect,
                            mask,
                        },
                    ))
                    .id();
                Ok(map_entity)
            // }
            // #[cfg(feature = "level")]
            // SpriteMap::Level(map) => Ok(map.map(screen_start, 0, &mut self.commands)),
        // }
    }

    pub fn mget(
        &self,
        pos: Vec2,
        map_index: Option<usize>,
        _layer_index: Option<usize>,
    ) -> Result<usize, Error> {
        let map: &SpriteMap = self.sprite_map(map_index)?;
        match *map {
            SpriteMap::P8(ref handle) => {
                let map = self.p8_maps.get(handle).ok_or_else(|| Error::NoSuch("map for handle".into()))?;
                let i = (pos.x as u32 + pos.y as u32 * MAP_COLUMNS) as usize;
                Ok(*map.get(i).ok_or_else(|| Error::NoSuch(format!("map index {i} with length {} {}", map.len(), if i > 0x1000 { "; consider using the '--shared-data=map' argument." } else { "" }).into()))? as usize)
            }

            #[cfg(feature = "level")]
            SpriteMap::Level(ref map) => self.tiled.mget(map, pos, map_index, _layer_index).ok(),
        }
    }

    pub fn mset(
        &mut self,
        pos: Vec2,
        sprite_index: usize,
        map_index: Option<usize>,
        _layer_index: Option<usize>,
    ) -> Result<(), Error> {
        let map = self.sprite_map(map_index)?.clone();
        match map {
            SpriteMap::P8(handle) => {

                let map = self.p8_maps.get_mut(&handle).ok_or_else(|| Error::NoSuch("map for handle".into()))?;
                map
                .get_mut((pos.x as u32 + pos.y as u32 * MAP_COLUMNS) as usize)
                .map(|value| *value = sprite_index as u8)
                .ok_or_else(|| Error::NoSuch("map entry".into()))
            }
            #[cfg(feature = "level")]
            SpriteMap::Level(ref mut map) => {
                todo!()
                // self.tiled
                //     .mset(map, pos, sprite_index, map_index, layer_index)
            }
        }
    }
}

#[cfg(feature = "scripting")]
mod lua {
    use super::*;
    use crate::{pico8::lua::with_pico8, DropPolicy, N9Entity};

    use bevy_mod_scripting::core::bindings::{
        function::{
            into_ref::IntoScriptRef,
            namespace::{GlobalNamespace, NamespaceBuilder},
            script_function::FunctionCallContext,
        },
        ReflectReference,
    };
    pub(crate) fn plugin(app: &mut App) {
        let world = app.world_mut();

        NamespaceBuilder::<GlobalNamespace>::new_unregistered(world)
            .register(
                "mget",
                |ctx: FunctionCallContext,
                 x: f32,
                 y: f32,
                 map_index: Option<usize>,
                 layer_index: Option<usize>| {
                    with_pico8(&ctx, move |pico8| {
                        pico8.mget(Vec2::new(x, y), map_index, layer_index)
                    })
                },
            )
            .register(
                "mset",
                |ctx: FunctionCallContext,
                 x: f32,
                 y: f32,
                 v: usize,
                 map_index: Option<usize>,
                 layer_index: Option<usize>| {
                    with_pico8(&ctx, move |pico8| {
                        pico8.mset(Vec2::new(x, y), v, map_index, layer_index)
                    })
                },
            )
            // map( celx, cely, sx, sy, celw, celh, [layer] )
            .register(
                "map",
                |ctx: FunctionCallContext,
                 celx: Option<u32>,
                 cely: Option<u32>,
                 sx: Option<f32>,
                 sy: Option<f32>,
                 celw: Option<u32>,
                 celh: Option<u32>,
                 layer: Option<u8>,
                 map_index: Option<usize>| {
                    let id = with_pico8(&ctx, move |pico8| {
                        pico8.map(
                            UVec2::new(celx.unwrap_or(0), cely.unwrap_or(0)),
                            Vec2::new(sx.unwrap_or(0.0), sy.unwrap_or(0.0)),
                            UVec2::new(celw.unwrap_or(128), celh.unwrap_or(128)),
                            layer,
                            map_index,
                        )
                    })?;

                    let entity = N9Entity {
                        entity: id,
                        drop: DropPolicy::Nothing,
                    };
                    let world = ctx.world()?;
                    let reference = {
                        let allocator = world.allocator();
                        let mut allocator = allocator.write();
                        ReflectReference::new_allocated(entity, &mut allocator)
                    };
                    ReflectReference::into_script_ref(reference, world)
                },
            );
    }
}