Level exports - tiles, nametables, collision
parent
dc10213f75
commit
f7b9216680
|
@ -1,23 +1,23 @@
|
|||
use std::{error::Error, fs::read_to_string};
|
||||
use image::DynamicImage;
|
||||
use image::{DynamicImage, GenericImageView};
|
||||
use roxmltree::Node;
|
||||
use crate::reskit::utility::print_warning;
|
||||
use super::ecs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TiledTilemap {
|
||||
tileset: TiledTileset,
|
||||
layers: Vec<Layer>,
|
||||
pub tileset: TiledTileset,
|
||||
pub layers: Vec<Layer>,
|
||||
ecs: Vec<ecs::Entity>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
tile_width: usize,
|
||||
tile_height: usize
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub tile_width: usize,
|
||||
pub tile_height: usize
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TiledTileset {
|
||||
image: DynamicImage
|
||||
pub image: DynamicImage
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -156,6 +156,10 @@ pub fn get_tiled_tilemap( path: &str ) -> Result<TiledTilemap, Box<dyn Error>> {
|
|||
let image = tileset.descendants().find( | node | node.tag_name() == "image".into() ).ok_or( "invalid file: no image object" )?;
|
||||
let image = image::open( image.attribute( "source" ).ok_or( "invalid file: no source attribute on image" )? )?;
|
||||
|
||||
// Image must be a multiple of 8 (md system)
|
||||
if image.width() % 8 != 0 { return Err( "invalid file: tileset width not multiple of 8" )? }
|
||||
if image.height() % 8 != 0 { return Err( "invalid file: tileset height not multiple of 8" )? }
|
||||
|
||||
// Get the layers
|
||||
let layers: Vec<Layer> = map.descendants()
|
||||
.filter( | node | node.tag_name() == "layer".into() )
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod converter;
|
||||
pub mod ecs;
|
||||
pub mod ecs;
|
||||
pub mod system;
|
|
@ -0,0 +1,152 @@
|
|||
use std::{error::Error, convert::TryInto};
|
||||
use crate::reskit::tileset::image_to_tiles;
|
||||
use super::converter::{TiledTilemap, Layer, SystemPlane};
|
||||
|
||||
/**
|
||||
* Output the .bin file (using `tileset` tool to build it) containing each of the tiles
|
||||
* in the Tiled Editor tileset.
|
||||
*/
|
||||
pub fn get_tiles( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut palette: [u16; 16] = [ 0; 16 ];
|
||||
let mut all_tiles: Vec<u8> = Vec::new();
|
||||
|
||||
for tile_y in 0..tilemap.tile_height {
|
||||
for tile_x in 0..tilemap.tile_width {
|
||||
let tile = tilemap.tileset.image.clone().crop(
|
||||
tile_x as u32,
|
||||
tile_y as u32,
|
||||
tilemap.tile_width as u32,
|
||||
tilemap.tile_height as u32
|
||||
);
|
||||
|
||||
let tile_bin = image_to_tiles(
|
||||
&tile,
|
||||
&mut palette,
|
||||
"tile"
|
||||
);
|
||||
|
||||
all_tiles.extend( tile_bin );
|
||||
}
|
||||
}
|
||||
|
||||
// Define result and write palette to top of result
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
for i in 0..palette.len() {
|
||||
let bytes = palette[ i ].to_be_bytes();
|
||||
for i in 0..2 {
|
||||
result.push( bytes[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Extend all tiles onto result
|
||||
result.extend( all_tiles );
|
||||
|
||||
Ok( result )
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the .map file defining the hardware tilemap for the given Tiled Tilemap.
|
||||
* In `--system md`, this outputs tilemap B and then tilemap A
|
||||
*/
|
||||
pub fn get_tilemap( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let layer_b: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Tile { system_plane: SystemPlane::MdPlaneB, tiles: _ } ) );
|
||||
let layer_a: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Tile { system_plane: SystemPlane::MdPlaneA, tiles: _ } ) );
|
||||
|
||||
let md_height_per_tile = tilemap.tile_height / 8;
|
||||
let md_width_per_tile = tilemap.tile_width / 8;
|
||||
|
||||
// Each entry in a `--system md` tilemap is 16 bits
|
||||
let mut nametable: Vec<u16> = vec![ 0; ( tilemap.width * md_width_per_tile ) * ( tilemap.height * md_height_per_tile ) ];
|
||||
|
||||
if let Some( layer_b ) = layer_b {
|
||||
let layer_b = match layer_b {
|
||||
Layer::Tile { system_plane: _, tiles } => tiles,
|
||||
_ => return Err( "internal error: invalid object type" )?
|
||||
};
|
||||
|
||||
for y in 0..tilemap.height {
|
||||
for x in 0..tilemap.width {
|
||||
let target_tile: u32 = *layer_b.get( ( y * tilemap.width ) + x ).ok_or( "internal error: invalid data in tilemap" )?;
|
||||
let target_tile: u16 = target_tile.try_into()?;
|
||||
if target_tile > 0 {
|
||||
let wide_tile_size = md_height_per_tile as u16 * md_width_per_tile as u16;
|
||||
let source_tile = ( target_tile - 1 ) * wide_tile_size;
|
||||
let x = x * md_width_per_tile;
|
||||
let y = y * md_height_per_tile;
|
||||
|
||||
// From the starting point of x, y "stamp" the target tile's indices
|
||||
let mut revolving_counter = 0;
|
||||
for y_stamp in y..( y + md_height_per_tile ) {
|
||||
for x_stamp in x..( x + md_width_per_tile ) {
|
||||
nametable[ ( y_stamp * ( tilemap.width * md_width_per_tile ) ) + x_stamp ] = source_tile + revolving_counter;
|
||||
revolving_counter = ( revolving_counter + 1 ) % wide_tile_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just do the same for layer a
|
||||
// Copy pasted because i'm lazy and tired
|
||||
if let Some( layer_a ) = layer_a {
|
||||
let layer_a = match layer_a {
|
||||
Layer::Tile { system_plane: _, tiles } => tiles,
|
||||
_ => return Err( "internal error: invalid object type" )?
|
||||
};
|
||||
|
||||
for y in 0..tilemap.height {
|
||||
for x in 0..tilemap.width {
|
||||
let target_tile: u32 = *layer_a.get( ( y * tilemap.width ) + x ).ok_or( "internal error: invalid data in tilemap" )?;
|
||||
let target_tile: u16 = target_tile.try_into()?;
|
||||
if target_tile > 0 {
|
||||
let wide_tile_size = md_height_per_tile as u16 * md_width_per_tile as u16;
|
||||
let source_tile = ( target_tile - 1 ) * wide_tile_size;
|
||||
let x = x * md_width_per_tile;
|
||||
let y = y * md_height_per_tile;
|
||||
|
||||
// From the starting point of x, y "stamp" the target tile's indices
|
||||
let mut revolving_counter = 0;
|
||||
for y_stamp in y..( y + md_height_per_tile ) {
|
||||
for x_stamp in x..( x + md_width_per_tile ) {
|
||||
nametable[ ( y_stamp * ( tilemap.width * md_width_per_tile ) ) + x_stamp ] = source_tile + revolving_counter;
|
||||
revolving_counter = ( revolving_counter + 1 ) % wide_tile_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the u16's to a series of u8 data
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
for i in 0..nametable.len() {
|
||||
let bytes = nametable[ i ].to_be_bytes();
|
||||
for i in 0..2 {
|
||||
result.push( bytes[ i ] );
|
||||
}
|
||||
}
|
||||
Ok( result )
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the .lvc collision map (u8 sized for the map dimensions, collision areas are either 0 or 1)
|
||||
*/
|
||||
pub fn get_collision_map( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
|
||||
let collision: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Collision { tiles: _ } ) );
|
||||
if let Some( collision ) = collision {
|
||||
let collision = match collision {
|
||||
Layer::Collision { tiles } => tiles,
|
||||
_ => return Err( "internal error: invalid object type" )?
|
||||
};
|
||||
|
||||
for collision_data in collision {
|
||||
let collision_data: u8 = if *collision_data > 0 { 1 } else { 0 };
|
||||
result.push( collision_data );
|
||||
}
|
||||
}
|
||||
|
||||
Ok( result )
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use crate::reskit::utility;
|
||||
use std::process::exit;
|
||||
use std::{process::exit, error::Error};
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use image::{ GenericImageView, DynamicImage };
|
||||
|
||||
fn color_to_palette( r: u16, g: u16, b: u16, palette: &mut [u16; 16] ) -> u32 {
|
||||
pub fn color_to_palette( r: u16, g: u16, b: u16, palette: &mut [u16; 16] ) -> u32 {
|
||||
let final_val =
|
||||
( ( r & 0x00F0 ) >> 4 ) |
|
||||
( g & 0x00F0 ) |
|
||||
|
@ -41,7 +41,7 @@ fn get_pixel( image: &DynamicImage, palette: &mut [u16; 16], x: u32, y: u32 ) ->
|
|||
color_to_palette( pixel[ 0 ].into(), pixel[ 1 ].into(), pixel[ 2 ].into(), palette )
|
||||
}
|
||||
|
||||
fn output_bin( image_filename: &str, output_filename: &str, palette: [u16; 16], body: Vec<u8> ) {
|
||||
pub fn output_bin( output_filename: &str, palette: [u16; 16], body: Vec<u8> ) -> Result<(), Box<dyn Error>> {
|
||||
let mut output_palette: Vec< u8 > = Vec::new();
|
||||
for i in 0..palette.len() {
|
||||
let bytes = palette[ i ].to_be_bytes();
|
||||
|
@ -54,13 +54,14 @@ fn output_bin( image_filename: &str, output_filename: &str, palette: [u16; 16],
|
|||
if let Ok( mut output_file ) = output_try {
|
||||
output_file.write( &output_palette ).unwrap();
|
||||
output_file.write( &body ).unwrap();
|
||||
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
||||
|
||||
Ok( () )
|
||||
} else {
|
||||
utility::print_error( format!( "could not open filename for output {}", output_filename ).as_str() );
|
||||
return Err( format!( "could not open filename for output {}", output_filename ).as_str() )?;
|
||||
}
|
||||
}
|
||||
|
||||
fn output_inc( image_filename: &str, output_filename: &str, palette: [u16; 16], body: Vec<u8> ) {
|
||||
pub fn output_inc( output_filename: &str, palette: [u16; 16], body: Vec<u8> ) -> Result<(), Box<dyn Error>> {
|
||||
let mut output_palette: Vec< u8 > = Vec::new();
|
||||
for i in 0..palette.len() {
|
||||
let bytes = palette[ i ].to_be_bytes();
|
||||
|
@ -111,71 +112,85 @@ fn output_inc( image_filename: &str, output_filename: &str, palette: [u16; 16],
|
|||
fs::write( output_filename.to_string() + ".h", output_h ).expect( "Could not write header file" );
|
||||
fs::write( output_filename.to_string() + ".c", output_c ).expect( "Could not write source file" );
|
||||
|
||||
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
||||
Ok( () )
|
||||
}
|
||||
|
||||
pub fn image_to_tiles( img: &DynamicImage, palette: &mut [u16; 16], tile_order: &str ) -> Vec<u8> {
|
||||
let ( mut max_x, mut max_y ) = img.dimensions();
|
||||
if max_x % 8 != 0 { max_x = ( 8 * ( max_x / 8 ) ) + ( 8 - ( max_x % 8 ) ); }
|
||||
if max_y % 8 != 0 { max_y = ( 8 * ( max_y / 8 ) ) + ( 8 - ( max_y % 8 ) ); }
|
||||
|
||||
let mut body: Vec<u8> = Vec::new();
|
||||
|
||||
if tile_order == "sprite" {
|
||||
/*
|
||||
* Sprite order:
|
||||
* 1 3
|
||||
* 2 4
|
||||
*/
|
||||
for x in ( 0..max_x ).step_by( 8 ) {
|
||||
for y in ( 0..max_y ).step_by( 8 ) {
|
||||
for cell_y in 0..8 {
|
||||
let mut series: u32 = 0;
|
||||
|
||||
for cell_x in 0..8 {
|
||||
let nibble: u32 = get_pixel( &img, palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
||||
series = series | nibble;
|
||||
}
|
||||
|
||||
let bytes = series.to_be_bytes();
|
||||
for i in 0..4 {
|
||||
body.push( bytes[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Tile order:
|
||||
* 1 2
|
||||
* 3 4
|
||||
*/
|
||||
for y in ( 0..max_y ).step_by( 8 ) {
|
||||
for x in ( 0..max_x ).step_by( 8 ) {
|
||||
for cell_y in 0..8 {
|
||||
let mut series: u32 = 0;
|
||||
|
||||
for cell_x in 0..8 {
|
||||
let nibble: u32 = get_pixel( &img, palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
||||
series = series | nibble;
|
||||
}
|
||||
|
||||
let bytes = series.to_be_bytes();
|
||||
for i in 0..4 {
|
||||
body.push( bytes[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
pub fn generate( image_filename: &str, output_filename: &str, output_mode: &str, tile_order: &str ) {
|
||||
let img = image::open( image_filename );
|
||||
if let Ok( img ) = img {
|
||||
let ( mut max_x, mut max_y ) = img.dimensions();
|
||||
if max_x % 8 != 0 { max_x = ( 8 * ( max_x / 8 ) ) + ( 8 - ( max_x % 8 ) ); }
|
||||
if max_y % 8 != 0 { max_y = ( 8 * ( max_y / 8 ) ) + ( 8 - ( max_y % 8 ) ); }
|
||||
|
||||
let mut palette: [u16; 16] = [ 0; 16 ];
|
||||
let mut body: Vec< u8 > = Vec::new();
|
||||
|
||||
if tile_order == "sprite" {
|
||||
/*
|
||||
* Tile order:
|
||||
* 1 3
|
||||
* 2 4
|
||||
*/
|
||||
for x in ( 0..max_x ).step_by( 8 ) {
|
||||
for y in ( 0..max_y ).step_by( 8 ) {
|
||||
for cell_y in 0..8 {
|
||||
let mut series: u32 = 0;
|
||||
|
||||
for cell_x in 0..8 {
|
||||
let nibble: u32 = get_pixel( &img, &mut palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
||||
series = series | nibble;
|
||||
}
|
||||
|
||||
let bytes = series.to_be_bytes();
|
||||
for i in 0..4 {
|
||||
body.push( bytes[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Tile order:
|
||||
* 1 2
|
||||
* 3 4
|
||||
*/
|
||||
for y in ( 0..max_y ).step_by( 8 ) {
|
||||
for x in ( 0..max_x ).step_by( 8 ) {
|
||||
for cell_y in 0..8 {
|
||||
let mut series: u32 = 0;
|
||||
|
||||
for cell_x in 0..8 {
|
||||
let nibble: u32 = get_pixel( &img, &mut palette, cell_x + x, cell_y + y ) << ( ( 7 - cell_x ) * 4 );
|
||||
series = series | nibble;
|
||||
}
|
||||
|
||||
let bytes = series.to_be_bytes();
|
||||
for i in 0..4 {
|
||||
body.push( bytes[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let body = image_to_tiles( &img, &mut palette, tile_order );
|
||||
|
||||
if output_mode == "bin" {
|
||||
output_bin( image_filename, output_filename, palette, body );
|
||||
if let Err( err ) = output_bin( output_filename, palette, body ) {
|
||||
utility::print_error( &format!( "{}", err ) );
|
||||
}
|
||||
|
||||
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
||||
} else if output_mode == "inc" {
|
||||
output_inc( image_filename, output_filename, palette, body );
|
||||
if let Err( err ) = output_inc( output_filename, palette, body ) {
|
||||
utility::print_error( &format!( "{}", err ) );
|
||||
}
|
||||
|
||||
utility::print_good( format!( "converted file {}", image_filename ).as_str() );
|
||||
} else {
|
||||
utility::print_error( format!( "invalid output mode {}", output_mode ).as_str() );
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue