diff --git a/src/reskit/cli/evaluator.rs b/src/reskit/cli/evaluator.rs index 439789a..1109ae6 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, collections::HashMap}; use clap::Parser; -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, get_sprites}}}; +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_objs, get_sprites}}}; use super::settings::{Args, Tools, TileOutputFormat, TileOrder}; pub fn run_command() -> Result<(), Box> { @@ -65,34 +65,10 @@ pub fn run_command() -> Result<(), Box> { print_good( "all files converted successfully" ); } - Tools::Level { input_file, output_directory, component_ids, attribute_ids, symbol_ids, sprite_ids, console: _ } => { + Tools::Level { input_file, output_directory, fields, symbol_ids, sprite_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(); let mut all_sprites: 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 { @@ -116,10 +92,10 @@ pub fn run_command() -> Result<(), Box> { all_sprites.insert( symbol_name.to_owned(), symbol_id ); } - let component_ids = all_components; - let attribute_ids = all_attributes; let symbol_ids = all_symbols; let sprite_ids = all_sprites; + // TODO: will be used when custom object fields are implemented + let _fields: Vec<&str> = fields.split( "," ).collect(); let tiled_file = get_tiled_tilemap( &input_file )?; @@ -142,9 +118,9 @@ 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, &component_ids, &attribute_ids, &symbol_ids )? )?; - print_good( "exported objects.ecs" ); + let mut objs_bin = File::create( format!( "{}objects.obs", output_directory ) )?; + objs_bin.write_all( &get_objs( &tiled_file, &symbol_ids )? )?; + print_good( "exported objects.obs" ); let mut sprites_bin = File::create( format!( "{}sprites.spt", output_directory ) )?; sprites_bin.write_all( &get_sprites( &tiled_file, &sprite_ids )? )?; diff --git a/src/reskit/cli/settings.rs b/src/reskit/cli/settings.rs index 22e9145..b7ad2f3 100644 --- a/src/reskit/cli/settings.rs +++ b/src/reskit/cli/settings.rs @@ -109,15 +109,11 @@ 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 fields in order to define objects' struct (after object ID, position x, and position y) (format: ,,) + #[arg(long, default_value_t=String::from(""))] + fields: String, - /// Zero or more ECS component attributes (format: =, , ...) - #[arg(long, num_args(0..))] - attribute_ids: Vec, - - /// Zero or more ECS symbol ID definitions (format: =) + /// Zero or more symbol ID definitions used for string identifiers in objects (format: =) #[arg(long, num_args(0..))] symbol_ids: Vec, diff --git a/src/reskit/level/converter.rs b/src/reskit/level/converter.rs index 1b36a09..3e481c9 100644 --- a/src/reskit/level/converter.rs +++ b/src/reskit/level/converter.rs @@ -1,18 +1,24 @@ use std::{error::Error, fs::read_to_string, path::Path, borrow::Cow}; use image::{DynamicImage, GenericImageView}; +use linked_hash_map::LinkedHashMap; use roxmltree::Node; use crate::reskit::{utility::print_warning, cli::settings::TileOrder}; -use super::ecs; #[derive(Debug)] pub struct TiledTilemap { pub tileset: Vec, pub layers: Vec, - pub ecs: Vec, + pub objects: Vec, pub width: usize, pub height: usize } +#[derive(Debug)] +pub struct Object { + pub id: String, + pub attributes: LinkedHashMap +} + #[derive(Debug)] pub struct SpriteMetadata { pub id: String, @@ -201,6 +207,42 @@ fn get_tiles( tileset: Node, first_gid: usize, working_directory: &str ) -> Resu Ok( TiledTileset { first_gid, image, palettes, tile_order, sprite_metadata } ) } +pub fn get_objs( node: &Node ) -> Result, Box> { + let mut result = Vec::new(); + + let objects = node.descendants().filter( | node | node.tag_name() == "object".into() ); + for object in objects { + let object_id = object.attribute( "id" ).ok_or( "invalid file: object in objectgroup has no id attribute" )?; + + if let Some( properties ) = object.descendants().find( | node | node.tag_name() == "properties".into() ) { + if let Some( object_id_property ) = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-object-id" ) ) { + let id = object_id_property.attribute( "value" ).ok_or( "invalid file: property has no value attribute" )?.to_owned(); + + let mut attributes = LinkedHashMap::new(); + + // Get position x and y as beginning attributes + let ( x, y ) = ( + object.attribute( "x" ).ok_or( "invalid file: object has no x position" )?.to_owned(), + object.attribute( "y" ).ok_or( "invalid file: object has no y position" )?.to_owned() + ); + + attributes.insert( "x".trim().to_owned(), x ); + attributes.insert( "y".trim().to_owned(), y ); + + // TODO: Custom object fields + + result.push( Object { id, attributes } ); + } else { + print_warning( &format!( "object {} has no \"reskit-object-id\" property....ignoring this object. this is probably not what you want.", object_id ) ); + } + } else { + print_warning( &format!( "object {} has no properties....ignoring this object. this is probably not what you want", object_id ) ); + } + } + + Ok( result ) +} + pub fn get_tiled_tilemap( path: &str ) -> Result> { let file = read_to_string( path )?; let document = roxmltree::Document::parse( &file )?; @@ -294,8 +336,8 @@ pub fn get_tiled_tilemap( path: &str ) -> Result> { // Get the entity-component system let object_group = map.descendants().find( | node | node.tag_name() == "objectgroup".into() ); - let ecs = if let Some( object_group ) = object_group { - ecs::get_ecs( object_group )? + let objects = if let Some( object_group ) = object_group { + get_objs( &object_group )? } else { Vec::new() }; @@ -304,7 +346,7 @@ pub fn get_tiled_tilemap( path: &str ) -> Result> { TiledTilemap { tileset, layers, - ecs, + objects, width, height } diff --git a/src/reskit/level/system.rs b/src/reskit/level/system.rs index 78f1287..09b258d 100644 --- a/src/reskit/level/system.rs +++ b/src/reskit/level/system.rs @@ -215,145 +215,43 @@ pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result, Box, attribute_ids: &HashMap>, symbol_ids: &HashMap ) -> Result, Box> { - if tilemap.ecs.is_empty() { - return Ok( vec![] ) - } - +pub fn get_objs( tilemap: &TiledTilemap, symbol_ids: &HashMap ) -> Result, Box> { let mut result: Vec = Vec::new(); - // Build a complete set of types - let mut types: Vec> = Vec::new(); - for entity in &tilemap.ecs { - // Get the list of component IDs attached to this entity - let mut components: Vec = entity.components.keys() - .map( | id | Ok( *component_ids.get( id ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", id ) )? ) ) - .collect::, Box>>()?; - components.sort(); + // 2 bytes: Number of objects + result.extend( ( tilemap.objects.len() as u16 ).to_be_bytes() ); - // Assign this unique combination of components a type id by inserting it into `types` - if let None = types.iter().find( | ecs_type | ecs_type == &&components ) { - types.push( components ); - } - } + if tilemap.objects.len() > 0 { + let archetype = tilemap.objects.first().ok_or( "internal error: no first object" )?; - 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" )?; + // 2 bytes: Object struct size + result.extend( ( ( archetype.attributes.len() + 1 ) as u16 ).to_be_bytes() ); - // Type Table - // 2 bytes: Number of Types - result.extend( ( types.len() as u16 ).to_be_bytes() ); + // For (number of objects): + for object in &tilemap.objects { + // (Object struct size * 2): Data for object, ordered by --fields option, each one 16-bit field - // 2 bytes: Type Definition Union Size - result.extend( largest_type_size.to_be_bytes() ); + // First output the object ID + let object_id = symbol_ids.get( &object.id ).ok_or( format!( "invalid file: undefined symbol \"{}\"", object.id ) )?; + result.extend( object_id.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 = ecs_type.clone(); - - // 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 { - let mut components: Vec = entity.components.keys() - .map( | id | Ok( *component_ids.get( id ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", id ) )? ) ) - .collect::, Box>>()?; - components.sort(); - - // 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 ); - } - - // Find the largest size of a combination of attributes (the component attribute union size) - let largest_type_attributes_size = tilemap.ecs.iter() - .map( | entity | { - let mut total_attributes_for_entity = 0; - - for ( _, component ) in &entity.components { - total_attributes_for_entity += component.attributes.len() - } - - total_attributes_for_entity - } ) - .reduce( | prev, current | max( prev, current ) ) - .ok_or( "internal error: no largest type attributes size" )? as u16; - - // 2 bytes: Component Attribute Union Size - result.extend( largest_type_attributes_size.to_be_bytes() ); - - // For (Object Table Size): - for entity in &tilemap.ecs { - let mut written_attributes = 0; - - let mut components: Vec<(&String, &Component)> = entity.components.iter().collect(); - components.sort_by_key( | ( component_id, _ ) | component_ids[ *component_id ] ); - - for ( component_id, component ) in 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 ) )?; - - 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() ); - } + // Then output the attributes in the order provided via LinkedHashMap + for ( _attribute, value ) in &object.attributes { + let value: u16 = match value.as_str() { + "true" => 0x01, + "false" => 0x00, + all_else => { + if let Ok( valid_u16 ) = all_else.parse::() { + valid_u16 + } else { + *symbol_ids.get( all_else ).ok_or( format!( "invalid file: undefined symbol \"{}\"", all_else ) )? } } - } - } else { - print_info( &format!( "component \"{}\" has no defined attributes (command line option --attribute-ids)", component_id ) ); + }; + + result.extend( value.to_be_bytes() ); } } - - // Fill the remainder of the union with 0xFF, if needed - let remainder = largest_type_attributes_size - written_attributes; - for _ in 0..remainder { - result.extend( ( 0xFFFF as u16 ).to_be_bytes() ); - } - } - - // Fill the remainder of the sparse array with 0xFF, if needed - let remainder = 512 - tilemap.ecs.len(); - for _ in 0..remainder { - for _ in 0..largest_type_attributes_size { - result.extend( ( 0xFFFF as u16 ).to_be_bytes() ); - } } Ok( result ) @@ -440,7 +338,7 @@ pub fn get_code( tilemap: &TiledTilemap, level_name: &str, path_prefix: &str ) - incbin '{path_prefix}{level_name}/collision.lvc' {level_label}Objects: - incbin '{path_prefix}{level_name}/objects.ecs' + incbin '{path_prefix}{level_name}/objects.obs' {level_label}Sprites: incbin '{path_prefix}{level_name}/sprites.spt'