Remove entity-component system design (it ended up being types but worse)
parent
7a5bc41ce8
commit
27a6e09560
|
@ -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<dyn Error>> {
|
||||
|
@ -65,34 +65,10 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
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<String, u8> = HashMap::new();
|
||||
let mut all_symbols: HashMap<String, u16> = HashMap::new();
|
||||
let mut all_attributes: HashMap<String, Vec<String>> = HashMap::new();
|
||||
let mut all_sprites: HashMap<String, u16> = 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<String> = 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<dyn Error>> {
|
|||
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<dyn Error>> {
|
|||
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 )? )?;
|
||||
|
|
|
@ -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: <component_name>=<u8>)
|
||||
#[arg(long, num_args(0..))]
|
||||
component_ids: Vec<String>,
|
||||
/// Zero or more fields in order to define objects' struct (after object ID, position x, and position y) (format: <field1>,<field2>,<field3>)
|
||||
#[arg(long, default_value_t=String::from(""))]
|
||||
fields: String,
|
||||
|
||||
/// Zero or more ECS component attributes (format: <component_name>=<attribute_1>, <attribute_2>, ...)
|
||||
#[arg(long, num_args(0..))]
|
||||
attribute_ids: Vec<String>,
|
||||
|
||||
/// Zero or more ECS symbol ID definitions (format: <symbol_name>=<u8>)
|
||||
/// Zero or more symbol ID definitions used for string identifiers in objects (format: <symbol_name>=<u8>)
|
||||
#[arg(long, num_args(0..))]
|
||||
symbol_ids: Vec<String>,
|
||||
|
||||
|
|
|
@ -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<TiledTileset>,
|
||||
pub layers: Vec<Layer>,
|
||||
pub ecs: Vec<ecs::Entity>,
|
||||
pub objects: Vec<Object>,
|
||||
pub width: usize,
|
||||
pub height: usize
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Object {
|
||||
pub id: String,
|
||||
pub attributes: LinkedHashMap<String, String>
|
||||
}
|
||||
|
||||
#[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<Vec<Object>, Box<dyn Error>> {
|
||||
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<TiledTilemap, Box<dyn Error>> {
|
||||
let file = read_to_string( path )?;
|
||||
let document = roxmltree::Document::parse( &file )?;
|
||||
|
@ -294,8 +336,8 @@ pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
|
|||
|
||||
// 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, Box<dyn Error>> {
|
|||
TiledTilemap {
|
||||
tileset,
|
||||
layers,
|
||||
ecs,
|
||||
objects,
|
||||
width,
|
||||
height
|
||||
}
|
||||
|
|
|
@ -215,146 +215,44 @@ pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Er
|
|||
Ok( result )
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity-component system defined. If none is defined return an empty vec.
|
||||
*/
|
||||
pub fn get_ecs( tilemap: &TiledTilemap, component_ids: &HashMap<String, u8>, attribute_ids: &HashMap<String, Vec<String>>, symbol_ids: &HashMap<String, u16> ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
if tilemap.ecs.is_empty() {
|
||||
return Ok( vec![] )
|
||||
}
|
||||
|
||||
pub fn get_objs( tilemap: &TiledTilemap, symbol_ids: &HashMap<String, u16> ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
|
||||
// Build a complete set of types
|
||||
let mut types: Vec<Vec<u8>> = Vec::new();
|
||||
for entity in &tilemap.ecs {
|
||||
// Get the list of component IDs attached to this entity
|
||||
let mut components: Vec<u8> = entity.components.keys()
|
||||
.map( | id | Ok( *component_ids.get( id ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", id ) )? ) )
|
||||
.collect::<Result<Vec<u8>, Box<dyn Error>>>()?;
|
||||
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<u8> = 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<u8> = entity.components.keys()
|
||||
.map( | id | Ok( *component_ids.get( id ).ok_or( format!( "invalid file: when building type table: undefined component \"{}\"", id ) )? ) )
|
||||
.collect::<Result<Vec<u8>, Box<dyn Error>>>()?;
|
||||
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<u16, ParseIntError> = string.parse();
|
||||
if let Ok( numeric ) = try_as_u16 {
|
||||
result.extend( numeric.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::<u16>() {
|
||||
valid_u16
|
||||
} else {
|
||||
let id: u16 = *symbol_ids.get( string ).ok_or( format!( "invalid file: undefined symbol \"{}\"", string ) )?;
|
||||
*symbol_ids.get( all_else ).ok_or( format!( "invalid file: undefined symbol \"{}\"", all_else ) )?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
result.extend( id.to_be_bytes() );
|
||||
result.extend( value.to_be_bytes() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print_info( &format!( "component \"{}\" has no defined attributes (command line option --attribute-ids)", component_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
|
|
Loading…
Reference in New Issue