diff --git a/src/reskit/cli/evaluator.rs b/src/reskit/cli/evaluator.rs index b81570a..07199c6 100644 --- a/src/reskit/cli/evaluator.rs +++ b/src/reskit/cli/evaluator.rs @@ -1,6 +1,6 @@ use std::{error::Error, fs::File, io::Write, path::Path}; use clap::Parser; -use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::{print_good, print_error, print_info}, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map}}}; +use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::{print_good, print_error, print_info}, level::{converter::get_tiled_tilemap, system::{get_tiles, get_code, get_tilemap, get_collision_map, get_ecs}}}; use super::settings::{Args, Tools, TileOutputFormat, TileOrder}; pub fn run_command() -> Result<(), Box> { @@ -87,6 +87,10 @@ pub fn run_command() -> Result<(), Box> { nametables_bin.write_all( &get_collision_map( &tiled_file )? )?; print_good( "exported collision.lvc" ); + let mut nametables_bin = File::create( format!( "{}objects.ecs", output_directory ) )?; + nametables_bin.write_all( &get_ecs( &tiled_file )? )?; + print_good( "exported objects.ecs" ); + let mut code_asm = File::create( format!( "{}level.asm", output_directory ) )?; code_asm.write_all( &get_code( &tiled_file, "testlevel", "levels/" )?.as_bytes() )?; print_good( "exported level.asm" ); diff --git a/src/reskit/level/converter.rs b/src/reskit/level/converter.rs index a44bc6d..f930661 100644 --- a/src/reskit/level/converter.rs +++ b/src/reskit/level/converter.rs @@ -8,7 +8,7 @@ use super::ecs; pub struct TiledTilemap { pub tileset: Vec, pub layers: Vec, - ecs: Vec, + pub ecs: Vec, pub width: usize, pub height: usize } diff --git a/src/reskit/level/system.rs b/src/reskit/level/system.rs index 494bd10..22edc2f 100644 --- a/src/reskit/level/system.rs +++ b/src/reskit/level/system.rs @@ -1,6 +1,7 @@ -use std::{error::Error, convert::TryInto}; +use std::{error::Error, convert::TryInto, cmp::max}; use image::GenericImageView; -use crate::reskit::{tileset::image_to_tiles, utility::symbol_to_pascal, cli::settings::TileOrder}; +use linked_hash_set::LinkedHashSet; +use crate::reskit::{tileset::image_to_tiles, utility::{symbol_to_pascal, print_info}, cli::settings::TileOrder}; use super::converter::{TiledTilemap, Layer, SystemPlane}; /** @@ -215,6 +216,101 @@ pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result, Box Result, Box> { + if tilemap.ecs.is_empty() { + return Ok( vec![] ) + } + + let mut result: Vec = Vec::new(); + + // Build a complete set of component IDs and types + let mut component_ids: LinkedHashSet = LinkedHashSet::new(); + let mut types: LinkedHashSet> = LinkedHashSet::new(); + for entity in &tilemap.ecs { + // Get the list of components attached to this entity + // Sort alphabetically as order matters in LinkedHashSets + let mut components: Vec = entity.components.keys().map( | id | id.to_lowercase() ).collect(); + components.sort(); + let components: LinkedHashSet = components.into_iter().collect(); + + // Assign an index to each unique component id by inserting it into `component_ids` + component_ids.extend( components.clone() ); + + // Assign this unique combination of components a type id + // by inserting it into `types` + types.insert( components ); + } + + let largest_type_size: u16 = types.iter() + .map( | components | components.len() as u16 ) + .reduce( | prev, current | max( prev, current ) ) + .ok_or( "internal error: type is empty" )?; + + // Type Table + // 2 bytes: Number of Types + result.extend( ( types.len() as u16 ).to_be_bytes() ); + + // 2 bytes: Type Definition Union Size + result.extend( largest_type_size.to_be_bytes() ); + + // For (Number of Types): + for ecs_type in &types { + // (Type Definition Union Size) bytes: List of Component IDs + let mut type_component_ids: Vec = vec![]; + + for component in ecs_type { + type_component_ids.push( + component_ids.iter().position( | value | component == value ).ok_or( "internal error: no component" )? as u8 + ); + } + + // Fill the remainder of the union with 0xFF, if needed + let remainder = largest_type_size as usize - type_component_ids.len(); + for _ in 0..remainder { + type_component_ids.push( 0xFF ); + } + + result.extend( type_component_ids ); + } + + // Object Table + // 2 bytes: Object Table Size + result.extend( ( 512 as u16 ).to_be_bytes() ); + + // For (Object Table Size) + for entity in &tilemap.ecs { + // Do the thing again + let mut components: Vec = entity.components.keys().map( | id | id.to_lowercase() ).collect(); + components.sort(); + let components: LinkedHashSet = components.into_iter().collect(); + + // What type id is this? + let type_id: u8 = types.iter().position( | ecs_type | ecs_type == &components ).ok_or( "internal error: no type id" )? as u8; + + // Output type id + result.push( type_id ); + } + + // Fill the remainder of the sparse Object Array with 0xFF, if needed + let remainder = 512 - tilemap.ecs.len(); + for _ in 0..remainder { + result.push( 0xFF ); + } + + // TODO: Component Attribute Table + + // Output IDs to terminal + let component_ids: Vec = component_ids.into_iter().collect(); + for i in 0..component_ids.len() { + print_info( &format!( "Component ID {}: {}", i, component_ids[ i ] ) ); + } + + Ok( result ) +} + /** * Get a helper .asm or .c file that ties all the level components together */ @@ -246,12 +342,16 @@ pub fn get_code( tilemap: &TiledTilemap, level_name: &str, path_prefix: &str ) - {level_label}Collision: incbin '{path_prefix}{level_name}/collision.lvc' +{level_label}Objects: + incbin '{path_prefix}{level_name}/objects.ecs' + {level_label}: dc.w {width}, {height}, {num_tiles} dc.l {level_label}Tiles dc.l {level_label}Palettes dc.l {level_label}Nametables - dc.l {level_label}Collision"# ); + dc.l {level_label}Collision + dc.l {level_label}Objects"# ); Ok( file ) } \ No newline at end of file