diff --git a/src/reskit/cli/evaluator.rs b/src/reskit/cli/evaluator.rs index 07199c6..c93cd2f 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 std::{error::Error, fs::File, io::Write, path::Path, collections::HashMap}; 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, get_ecs}}}; +use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::print_good, 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> { @@ -65,7 +65,49 @@ pub fn run_command() -> Result<(), Box> { print_good( "all files converted successfully" ); } - Tools::Level { input_file, output_directory, console: _ } => { + Tools::Level { input_file, output_directory, component_ids, attribute_ids, symbol_ids, console: _ } => { + // Clap can't do it. Sad! + let mut all_components: HashMap = HashMap::new(); + let mut all_symbols: HashMap = HashMap::new(); + let mut all_attributes: HashMap> = HashMap::new(); + for pair in component_ids { + let components: Vec<&str> = pair.split( "=" ).collect(); + if components.len() != 2 { + return Err( format!( "invalid format for component_ids: {}", pair ) )?; + } + + let component_name = components[ 0 ]; + let component_id: u8 = components[ 1 ].parse()?; + + all_components.insert( component_name.to_owned(), component_id ); + } + for pair in attribute_ids { + let components: Vec<&str> = pair.split( "=" ).collect(); + if components.len() != 2 { + return Err( format!( "invalid format for attribute_ids: {}", pair ) )?; + } + + let component_name = components[ 0 ]; + let attributes: Vec = components[ 1 ].split( "," ).map( | string | string.to_owned() ).collect(); + + all_attributes.insert( component_name.to_owned(), attributes ); + } + for pair in symbol_ids { + let symbols: Vec<&str> = pair.split( "=" ).collect(); + if symbols.len() != 2 { + return Err( format!( "invalid format for symbol_ids: {}", pair ) )?; + } + + let symbol_name = symbols[ 0 ]; + let symbol_id: u16 = symbols[ 1 ].parse()?; + + all_symbols.insert( symbol_name.to_owned(), symbol_id ); + } + + let component_ids = all_components; + let attribute_ids = all_attributes; + let symbol_ids = all_symbols; + let tiled_file = get_tiled_tilemap( &input_file )?; // Get tile and palette files @@ -88,7 +130,7 @@ pub fn run_command() -> Result<(), Box> { print_good( "exported collision.lvc" ); let mut nametables_bin = File::create( format!( "{}objects.ecs", output_directory ) )?; - nametables_bin.write_all( &get_ecs( &tiled_file )? )?; + nametables_bin.write_all( &get_ecs( &tiled_file, &component_ids, &attribute_ids, &symbol_ids )? )?; print_good( "exported objects.ecs" ); let mut code_asm = File::create( format!( "{}level.asm", output_directory ) )?; diff --git a/src/reskit/cli/settings.rs b/src/reskit/cli/settings.rs index 381a99d..ad28414 100644 --- a/src/reskit/cli/settings.rs +++ b/src/reskit/cli/settings.rs @@ -109,6 +109,18 @@ pub enum Tools { #[arg(short, long, default_value_t=String::from("./"))] output_directory: String, + /// Zero or more ECS component ID definitions (format: =) + #[arg(long, num_args(0..))] + component_ids: Vec, + + /// Zero or more ECS component attributes (format: =, , ...) + #[arg(long, num_args(0..))] + attribute_ids: Vec, + + /// Zero or more ECS symbol ID definitions (format: =) + #[arg(long, num_args(0..))] + symbol_ids: Vec, + /// Console system type #[arg(short, long, value_enum, default_value_t=SystemType::Md)] console: SystemType diff --git a/src/reskit/level/system.rs b/src/reskit/level/system.rs index b3a3e30..088271c 100644 --- a/src/reskit/level/system.rs +++ b/src/reskit/level/system.rs @@ -1,4 +1,4 @@ -use std::{error::Error, convert::TryInto, cmp::max, num::ParseIntError}; +use std::{error::Error, convert::TryInto, cmp::max, num::ParseIntError, collections::HashMap}; use image::GenericImageView; use linked_hash_map::LinkedHashMap; use linked_hash_set::LinkedHashSet; @@ -220,25 +220,18 @@ pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result, Box Result, Box> { +pub fn get_ecs( tilemap: &TiledTilemap, component_ids: &HashMap, attribute_ids: &HashMap>, symbol_ids: &HashMap ) -> 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(); + // Build a complete set of types 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() ); + let components: LinkedHashSet = entity.components.keys().map( | id | id.to_lowercase() ).collect(); // Assign this unique combination of components a type id // by inserting it into `types` @@ -264,7 +257,7 @@ pub fn get_ecs( tilemap: &TiledTilemap ) -> Result, Box> { for component in ecs_type { type_component_ids.push( - component_ids.iter().position( | value | component == value ).ok_or( "internal error: no component" )? as u8 + *component_ids.get( component ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", component ) )? ); } @@ -301,27 +294,6 @@ pub fn get_ecs( tilemap: &TiledTilemap ) -> Result, Box> { result.push( 0xFF ); } - // Component Attribute Table - // Index all attribute values that do not parse to u16 - let mut attribute_value_ids: LinkedHashSet = LinkedHashSet::new(); - for entity in &tilemap.ecs { - for ( _, component ) in &entity.components { - for ( _, attribute_value ) in &component.attributes { - // Handling various types from Tiled Editor - // * If it is "true" or "false", leave it alone. It will be articulated as 1 or 0. - // * If it can be parsed as a u16, leave it alone. - // * In all other cases, add to attribute_value_ids to create an index. - if attribute_value != "true" && attribute_value != "false" { - let try_as_u16: Result = attribute_value.parse(); - if let Err( _ ) = try_as_u16 { - attribute_value_ids.insert_if_absent( attribute_value.clone() ); - print_info( &format!( "Attribute Value ID {}: \"{}\"", attribute_value_ids.len() - 1, attribute_value ) ); - } - } - } - } - } - // Find the largest size of a combination of attributes (the component attribute union size) let largest_type_attributes_size = tilemap.ecs.iter() .map( | entity | { @@ -341,35 +313,32 @@ pub fn get_ecs( tilemap: &TiledTilemap ) -> Result, Box> { // For (Object Table Size): for entity in &tilemap.ecs { - // (Component Attribute Union Size) bytes: Component attribute settings - // Sort components in this entity alphabetically, ascending - let mut components: Vec<(&String, &Component)> = entity.components.iter().collect(); - components.sort_by( | a, b | a.0.partial_cmp( b.0 ).expect( "internal error: unsortable" ) ); - let mut written_attributes = 0; - for ( _, component ) in &components { - // Sort attributes in each component alphabetically - let mut attributes: Vec<(&String, &String)> = component.attributes.iter().collect(); - attributes.sort_by( | a, b | a.0.partial_cmp( b.0 ).expect( "internal error: unsortable" ) ); - for ( _, attribute_value ) in attributes { - written_attributes += 1; - match attribute_value.as_str() { - "true" => result.extend( ( 1 as u16 ).to_be_bytes() ), - "false" => result.extend( ( 0 as u16 ).to_be_bytes() ), - string => { - let try_as_u16: Result = string.parse(); - if let Ok( numeric ) = try_as_u16 { - result.extend( numeric.to_be_bytes() ); - } else { - let id: u16 = attribute_value_ids.iter() - .position( | attribute_value | attribute_value == &string ) - .ok_or( "internal error: no type id" )? as u16; + for ( component_id, component ) in &entity.components { + let attributes_in_order = attribute_ids.get( component_id ); + if let Some( attributes_in_order ) = attributes_in_order { + for attribute_id in attributes_in_order { + let attribute_value = component.attributes.get( attribute_id ).ok_or( format!( "invalid file: undefined attribute \"{}\" for component \"{}\"", attribute_id, component_id ) )?; - result.extend( id.to_be_bytes() ); + written_attributes += 1; + match attribute_value.as_str() { + "true" => result.extend( ( 1 as u16 ).to_be_bytes() ), + "false" => result.extend( ( 0 as u16 ).to_be_bytes() ), + string => { + let try_as_u16: Result = string.parse(); + if let Ok( numeric ) = try_as_u16 { + result.extend( numeric.to_be_bytes() ); + } else { + let id: u16 = *symbol_ids.get( string ).ok_or( format!( "invalid file: undefined symbol \"{}\"", string ) )?; + + result.extend( id.to_be_bytes() ); + } } } } + } else { + print_info( &format!( "component \"{}\" has no defined attributes (command line option --attribute-ids)", component_id ) ); } } @@ -388,46 +357,6 @@ pub fn get_ecs( tilemap: &TiledTilemap ) -> Result, Box> { } } - // 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 ] ) ); - } - - // Output type structures to terminal - let mut visited_types: LinkedHashSet> = LinkedHashSet::new(); - 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(); - - if visited_types.insert( components.clone() ) { - // 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; - print_info( &format!( "Type ID {} Components and Attributes:", type_id ) ); - - let mut components: Vec<(&String, &Component)> = entity.components.iter().collect(); - components.sort_by( | a, b | a.0.partial_cmp( b.0 ).expect( "internal error: unsortable" ) ); - - for ( component_name, component ) in components { - print_info( &format!( "\tComponent {}", component_name ) ); - let mut attributes: Vec<(&String, &String)> = component.attributes.iter().collect(); - attributes.sort_by( | a, b | a.0.partial_cmp( b.0 ).expect( "internal error: unsortable" ) ); - - let mut index = 0; - for ( attribute_name, _ ) in &attributes { - print_info( &format!( "\t\tAttribute ID {}: {}", index, attribute_name ) ); - index += 1; - } - - if attributes.is_empty() { - print_info( "\t\tNo attributes" ); - } - } - } - } - Ok( result ) } @@ -437,7 +366,6 @@ pub fn get_ecs( tilemap: &TiledTilemap ) -> Result, Box> { pub fn get_code( tilemap: &TiledTilemap, level_name: &str, path_prefix: &str ) -> Result> { let version = env!( "CARGO_PKG_VERSION" ); let level_label = symbol_to_pascal( level_name ); - let level_label_const_caps = level_label.to_uppercase(); let ( width, height ) = ( tilemap.width, tilemap.height ); let num_tiles = { let mut total_tiles = 0; @@ -448,71 +376,9 @@ pub fn get_code( tilemap: &TiledTilemap, level_name: &str, path_prefix: &str ) - total_tiles }; - - let mut constants: String = String::new(); - - // Output IDs for each type defined in the map - let mut component_ids: LinkedHashSet = LinkedHashSet::new(); - let mut attribute_ids: LinkedHashMap> = LinkedHashMap::new(); - let mut attribute_string_values: LinkedHashSet = LinkedHashSet::new(); - for entity in &tilemap.ecs { - let mut components: Vec = entity.components.keys().map( | id | id.to_lowercase() ).collect(); - components.sort(); - let components: LinkedHashSet = components.into_iter().collect(); - - component_ids.extend( components ); - - for ( component_name, component ) in &entity.components { - for ( _, attribute_value ) in &component.attributes { - match attribute_value.as_str() { - "true" | "false" => { /* nothing */ }, - string => { - let try_as_u16: Result = string.parse(); - if let Err( _ ) = try_as_u16 { - attribute_string_values.insert_if_absent( attribute_value.to_owned() ); - } - } - } - } - - let mut attributes: Vec = component.attributes.keys().map( | id | id.to_lowercase() ).collect(); - attributes.sort(); - let attribues: LinkedHashSet = attributes.into_iter().collect(); - - attribute_ids.entry( component_name.to_owned() ).or_default().extend( attribues ); - } - } - - let attribute_string_values: Vec = attribute_string_values.into_iter().collect(); - for i in 0..attribute_string_values.len() { - let attr_val = &attribute_string_values[ i ]; - match attr_val.as_str() { - "true" | "false" => { /* nothing */ }, - string => { - let try_as_u16: Result = string.parse(); - if let Err( _ ) = try_as_u16 { - constants += &format!( "{}_ATTR_{} = {}\n", level_label_const_caps, attribute_string_values[ i ].to_uppercase(), i ); - } - } - } - } - - let component_ids: Vec = component_ids.into_iter().collect(); - for i in 0..component_ids.len() { - let component_id = &component_ids[ i ]; - constants += &format!( "{}_{} = {}\n", level_label_const_caps, component_id.to_uppercase(), i ); - - let attribute_ids: Vec = attribute_ids[ component_id ].iter().map( | string | string.clone() ).collect(); - for i in 0..attribute_ids.len() { - constants += &format!( "{}_{}_{} = {}\n", level_label_const_caps, component_id.to_uppercase(), attribute_ids[ i ].to_uppercase(), i ); - } - - } - let file = format!( r#"; Level definition file ; Generated by reskit v{version} -{constants} {level_label}Tiles: incbin '{path_prefix}{level_name}/tiles.bin'