Export sprite table
parent
485d1d0304
commit
73b5ba6cc7
|
@ -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}}};
|
||||
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 super::settings::{Args, Tools, TileOutputFormat, TileOrder};
|
||||
|
||||
pub fn run_command() -> Result<(), Box<dyn Error>> {
|
||||
|
@ -65,11 +65,12 @@ 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, console: _ } => {
|
||||
Tools::Level { input_file, output_directory, component_ids, attribute_ids, 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 {
|
||||
|
@ -103,10 +104,22 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
all_symbols.insert( symbol_name.to_owned(), symbol_id );
|
||||
}
|
||||
for pair in sprite_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_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;
|
||||
|
||||
let tiled_file = get_tiled_tilemap( &input_file )?;
|
||||
|
||||
|
@ -133,6 +146,10 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
|
|||
nametables_bin.write_all( &get_ecs( &tiled_file, &component_ids, &attribute_ids, &symbol_ids )? )?;
|
||||
print_good( "exported objects.ecs" );
|
||||
|
||||
let mut sprites_bin = File::create( format!( "{}sprites.spt", output_directory ) )?;
|
||||
sprites_bin.write_all( &get_sprites( &tiled_file, &sprite_ids )? )?;
|
||||
print_good( "exported sprites.spt" );
|
||||
|
||||
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" );
|
||||
|
|
|
@ -121,6 +121,10 @@ pub enum Tools {
|
|||
#[arg(long, num_args(0..))]
|
||||
symbol_ids: Vec<String>,
|
||||
|
||||
/// Zero or more sprite ID definitions (format: <sprite_name_1>=<sprite_id_1>, <sprite_name_2>=<sprite_id_2>, ... )
|
||||
#[arg(long, num_args(0..))]
|
||||
sprite_ids: Vec<String>,
|
||||
|
||||
/// Console system type
|
||||
#[arg(short, long, value_enum, default_value_t=SystemType::Md)]
|
||||
console: SystemType
|
||||
|
|
|
@ -13,12 +13,21 @@ pub struct TiledTilemap {
|
|||
pub height: usize
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpriteMetadata {
|
||||
pub id: String,
|
||||
pub width: u8,
|
||||
pub height: u8,
|
||||
pub anim_interval: Option<u16>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TiledTileset {
|
||||
pub first_gid: usize,
|
||||
pub image: DynamicImage,
|
||||
pub palettes: Vec<Option<u8>>,
|
||||
pub tile_order: TileOrder
|
||||
pub tile_order: TileOrder,
|
||||
pub sprite_metadata: Option<SpriteMetadata>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -138,26 +147,58 @@ fn get_tiles( tileset: Node, first_gid: usize, working_directory: &str ) -> Resu
|
|||
}
|
||||
}
|
||||
|
||||
let sprite_metadata;
|
||||
|
||||
// Retrieve the tile order as specified by reskit-tile-order (if none is specified, fallback on Tile)
|
||||
let tile_order = if let Some( properties ) = tileset.descendants().find( | node | node.tag_name() == "properties".into() ) {
|
||||
if let Some( tile_order_property ) = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-tile-order" ) ) {
|
||||
let tile_order_property = tile_order_property.attribute( "value" ).expect( "internal error: no reskit-tile-order value" );
|
||||
let tile_order_property = tile_order_property.attribute( "value" ).expect( "invalid file: no reskit-tile-order value" );
|
||||
match tile_order_property.to_lowercase().as_str() {
|
||||
"sprite" => TileOrder::Sprite,
|
||||
"tile" => TileOrder::Tile,
|
||||
"sprite" => {
|
||||
// If sprite, reskit-sprite-height and reskit-sprite-width must be defined
|
||||
let reskit_sprite_height = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-sprite-height" ) ).ok_or( "invalid file: for reskit-tile-order \"sprite\", reskit-sprite-height and reskit-sprite-width must be defined." )?;
|
||||
let reskit_sprite_width = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-sprite-width" ) ).ok_or( "invalid file: for reskit-tile-order \"sprite\", reskit-sprite-height and reskit-sprite-width must be defined." )?;
|
||||
let reskit_sprite_id = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-sprite-id" ) ).ok_or( "invalid file: for reskit-tile-order \"sprite\", reskit-sprite-id must be defined." )?;
|
||||
let reskit_anim_interval = properties.descendants().find( | node | node.attribute( "name" ) == Some( "reskit-anim-interval" ) );
|
||||
|
||||
let reskit_sprite_height = reskit_sprite_height.attribute( "value" ).ok_or( "invalid file: no reskit-sprite-height value" )?;
|
||||
let reskit_sprite_width = reskit_sprite_width.attribute( "value" ).ok_or( "invalid file: no reskit-sprite-width value" )?;
|
||||
|
||||
let id: String = reskit_sprite_id.attribute( "value" ).ok_or( "invalid file: no reskit-sprite-id value" )?.to_owned();
|
||||
let width: u8 = reskit_sprite_width.parse()?;
|
||||
let height: u8 = reskit_sprite_height.parse()?;
|
||||
|
||||
let anim_interval = if let Some( property ) = reskit_anim_interval {
|
||||
let value = property.attribute( "value" ).ok_or( "invalid file: no reskit-anim-interval value" )?;
|
||||
Some( value.parse()? )
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
sprite_metadata = Some( SpriteMetadata { id, width, height, anim_interval } );
|
||||
|
||||
TileOrder::Sprite
|
||||
},
|
||||
"tile" => {
|
||||
sprite_metadata = None;
|
||||
TileOrder::Tile
|
||||
},
|
||||
invalid => {
|
||||
print_warning( &format!( "invalid setting for property reskit-tile-order: {}. falling back on \"sprite\"", invalid ) );
|
||||
sprite_metadata = None;
|
||||
print_warning( &format!( "invalid setting for property reskit-tile-order: {}. falling back on \"tile\"", invalid ) );
|
||||
TileOrder::Tile
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sprite_metadata = None;
|
||||
TileOrder::Tile
|
||||
}
|
||||
} else {
|
||||
sprite_metadata = None;
|
||||
TileOrder::Tile
|
||||
};
|
||||
|
||||
Ok( TiledTileset { first_gid, image, palettes, tile_order } )
|
||||
Ok( TiledTileset { first_gid, image, palettes, tile_order, sprite_metadata } )
|
||||
}
|
||||
|
||||
pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{error::Error, convert::TryInto, cmp::max, num::ParseIntError, collecti
|
|||
use image::GenericImageView;
|
||||
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};
|
||||
use super::converter::{TiledTilemap, Layer, SystemPlane, TiledTileset};
|
||||
|
||||
/**
|
||||
* Output the .bin and .pal file (using `tileset` tool to build it) containing each of the tiles
|
||||
|
@ -359,6 +359,55 @@ pub fn get_ecs( tilemap: &TiledTilemap, component_ids: &HashMap<String, u8>, att
|
|||
Ok( result )
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sprite table.
|
||||
*/
|
||||
pub fn get_sprites( tilemap: &TiledTilemap, sprite_ids: &HashMap<String, u16> ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
|
||||
let tiles_before = {
|
||||
let mut total_tiles = 0;
|
||||
|
||||
for tileset in &tilemap.tileset {
|
||||
total_tiles += ( tileset.image.width() / 8 ) * ( tileset.image.height() / 8 );
|
||||
}
|
||||
|
||||
total_tiles
|
||||
};
|
||||
let mut tiles_after: u32 = 0;
|
||||
|
||||
let sprites: Vec<&TiledTileset> = tilemap.tileset.iter().filter( | tileset | matches!( tileset.tile_order, TileOrder::Sprite ) ).collect();
|
||||
result.extend( ( sprites.len() as u16 ).to_be_bytes() );
|
||||
for sprite in sprites {
|
||||
let sprite_metadata = sprite.sprite_metadata.as_ref().ok_or( "internal error: tile order is sprite but no sprite data" )?;
|
||||
|
||||
let sprite_id = sprite_ids.get( &sprite_metadata.id ).ok_or( format!( "invalid file: undefined sprite id \"{}\"", sprite_metadata.id ) )?;
|
||||
result.extend( sprite_id.to_be_bytes() );
|
||||
|
||||
let tile_index_location = tiles_before + tiles_after;
|
||||
result.extend( ( tile_index_location as u16 ).to_be_bytes() );
|
||||
tiles_after += ( sprite_metadata.width * sprite_metadata.height ) as u32;
|
||||
|
||||
result.push( sprite_metadata.width );
|
||||
|
||||
result.push( sprite_metadata.height );
|
||||
|
||||
// One animation per .tsx file
|
||||
// Animations run like a filmstrip across, never down
|
||||
let tiles_across = sprite.image.width() / 8;
|
||||
let frames: u16 = ( tiles_across / ( sprite_metadata.width as u32 ) ) as u16;
|
||||
result.extend( frames.to_be_bytes() );
|
||||
|
||||
if let Some( jiffies ) = sprite_metadata.anim_interval {
|
||||
result.extend( jiffies.to_be_bytes() );
|
||||
} else {
|
||||
result.extend( ( 0 as u16 ).to_be_bytes() );
|
||||
};
|
||||
}
|
||||
|
||||
Ok( result )
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a helper .asm or .c file that ties all the level components together
|
||||
*/
|
||||
|
@ -393,14 +442,17 @@ pub fn get_code( tilemap: &TiledTilemap, level_name: &str, path_prefix: &str ) -
|
|||
{level_label}Objects:
|
||||
incbin '{path_prefix}{level_name}/objects.ecs'
|
||||
|
||||
{level_label}Sprites:
|
||||
incbin '{path_prefix}{level_name}/sprites.spt'
|
||||
|
||||
{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}Objects"# );
|
||||
|
||||
dc.l {level_label}Objects
|
||||
dc.l {level_label}Sprites"# );
|
||||
|
||||
Ok( file )
|
||||
}
|
Loading…
Reference in New Issue