diff --git a/Cargo.toml b/Cargo.toml index 2d00d1e..4801b23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,5 @@ pitch_shift = "1.0.0" linked_hash_set = "0.1.4" linked-hash-map = "0.5.6" convert_case = "0.6.0" -roxmltree = "0.18.0" \ No newline at end of file +roxmltree = "0.18.0" +regex = "1.9.5" \ No newline at end of file diff --git a/src/reskit/cli/evaluator.rs b/src/reskit/cli/evaluator.rs index 1330b70..2d1a854 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}, level::converter::get_tiled_tilemap}; +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}; use super::settings::{Args, Tools, TileOutputFormat, TileOrder}; pub fn run_command() -> Result<(), Box> { @@ -67,7 +67,7 @@ pub fn run_command() -> Result<(), Box> { } Tools::Level { input_file, output_directory, console, tile_size } => { let tiled_file = get_tiled_tilemap( &input_file )?; - + print_info( &format!( "{:#?}", tiled_file ) ); print_good( "file opened without errors" ); print_error( "unimplemented" ); } diff --git a/src/reskit/level/converter.rs b/src/reskit/level/converter.rs index 7d3ebd4..0a44134 100644 --- a/src/reskit/level/converter.rs +++ b/src/reskit/level/converter.rs @@ -1,27 +1,32 @@ use std::{error::Error, fs::read_to_string}; use image::DynamicImage; use roxmltree::Node; - use crate::reskit::utility::print_warning; +use super::ecs; +#[derive(Debug)] pub struct TiledTilemap { tileset: TiledTileset, layers: Vec, + ecs: Vec, width: usize, height: usize, tile_width: usize, tile_height: usize } +#[derive(Debug)] pub struct TiledTileset { image: DynamicImage } +#[derive(Debug)] pub enum SystemPlane { MdPlaneA, MdPlaneB } +#[derive(Debug)] pub enum Layer { Tile { system_plane: SystemPlane, @@ -165,10 +170,19 @@ pub fn get_tiled_tilemap( path: &str ) -> Result> { print_warning( "tile map has no \"collision\" layer: this probably is not what you want" ); } + // 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 )? + } else { + Vec::new() + }; + Ok( TiledTilemap { tileset: TiledTileset { image }, layers, + ecs, width, height, tile_width, diff --git a/src/reskit/level/ecs.rs b/src/reskit/level/ecs.rs new file mode 100644 index 0000000..5dc5de2 --- /dev/null +++ b/src/reskit/level/ecs.rs @@ -0,0 +1,100 @@ +use std::{error::Error, collections::HashMap}; +use regex::Regex; +use roxmltree::Node; +use crate::reskit::utility::print_warning; + +#[derive(Debug)] +pub struct Entity { + pub components: HashMap +} + +#[derive(Debug)] +pub struct Component { + pub attributes: HashMap +} + +pub fn get_ecs( object_group: Node ) -> Result, Box> { + let mut entities: Vec = Vec::new(); + + let objects: Vec = object_group.descendants().filter( | node | node.tag_name() == "object".into() ).collect(); + for object in objects { + let object_id = object.attribute( "id" ).ok_or( "invalid file: no id attribute set on object" )?; + let object_name = object.attribute( "name" ).unwrap_or( "" ); + let object_name = format!( "({}, ID: {})", object_id, object_name ); + + let mut entity: Entity = Entity { + components: HashMap::new() + }; + + // Get attributes for implicit `position` component + let x = object.attribute( "x" ).ok_or( "invalid file: position property not present on object" )?; + let y = object.attribute( "y" ).ok_or( "invalid file: position property not present on object" )?; + let width = object.attribute( "width" ).ok_or( "invalid file: position property not present on object" )?; + let height = object.attribute( "height" ).ok_or( "invalid file: position property not present on object" )?; + + entity.components.insert( + format!( "position" ), + Component { + attributes: HashMap::from( [ + ( format!( "x" ), x.to_owned() ), + ( format!( "y" ), y.to_owned() ), + ( format!( "width" ), width.to_owned() ), + ( format!( "height" ), height.to_owned() ) + ] ) + } + ); + + let properties = object.descendants().find( | node | node.tag_name() == "properties".into() ).ok_or( "invalid file: no properties in object" )?; + let properties = properties.descendants().filter( | node | node.tag_name() == "property".into() && node.attribute( "name" ).unwrap_or( "" ).starts_with( "reskit-component" ) ); + for component_property in properties { + let name = component_property.attribute( "name" ).ok_or( "internal error: name attribute expected" )?; + let prop_type = component_property.attribute( "type" ).unwrap_or( "string" ); + let value = component_property.attribute( "value" ); + + if let Some( value ) = value { + // Set up regex to remove individual parts + let regex = Regex::new( r#"reskit-component\[([a-z0-9_]+)\](\.[a-z0-9_]*)?"# )?; + if let Some( captures ) = regex.captures( name ) { + let component_name = captures.get( 1 ).ok_or( "internal error: regex did not match properly" )?.as_str(); + let attribute_name = captures.get( 2 ); + + if let Some( attribute_name ) = attribute_name { + // Setting is a component attribute setting ("value" is the setting) + let attribute_name = attribute_name.as_str().replace( ".", "" ); + if let Some( component ) = entity.components.get_mut( component_name ) { + component.attributes.insert( attribute_name, value.to_owned() ); + } else { + return Err( format!( "in object {}: undefined component in reskit-component definition \"{}\"", object_name, name ) )?; + } + } else { + // Setting is a component definition + if prop_type != "bool" { + print_warning( &format!( "in object {}: non-bool type reskit-component definition \"{}\". ignoring...", object_name, name ) ); + } else { + if value == "true" { + if entity.components.contains_key( component_name ) { + print_warning( &format!( "in object {}: duplicate reskit-component definition \"{}\". ignoring...", object_name, name ) ); + } else { + entity.components.insert( + component_name.to_owned(), + Component { attributes: HashMap::new() } + ); + } + } else { + print_warning( &format!( "in object {}: reskit-component definition \"{}\" is set to false. ignoring...", object_name, name ) ); + } + } + } + } else { + print_warning( &format!( "in object {}: invalid format for reskit-component attribute \"{}\". ignoring...", object_name, name ) ); + } + } else { + print_warning( &format!( "in object {}: no value for reskit-component attribute \"{}\". ignoring...", object_name, name ) ); + } + } + + entities.push( entity ); + } + + Ok( entities ) +} \ No newline at end of file diff --git a/src/reskit/level/mod.rs b/src/reskit/level/mod.rs index 9c6a24e..4f42881 100644 --- a/src/reskit/level/mod.rs +++ b/src/reskit/level/mod.rs @@ -1 +1,2 @@ pub mod converter; +pub mod ecs; \ No newline at end of file