WIP - New metatile converter
parent
18f1e20aa1
commit
6fe634c864
|
@ -1,6 +1,6 @@
|
||||||
use std::{error::Error, fs::File, io::Write, path::Path, collections::HashMap};
|
use std::{error::Error, fs::File, io::Write, path::Path, collections::HashMap};
|
||||||
use clap::Parser;
|
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_objs, get_sprites}}, easing::get_cubic_bezier};
|
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_metatile_maps, get_collision_map, get_objs, get_sprites}}, easing::get_cubic_bezier};
|
||||||
use super::settings::{Args, Tools, TileOutputFormat, TileOrder};
|
use super::settings::{Args, Tools, TileOutputFormat, TileOrder};
|
||||||
|
|
||||||
pub fn run_command() -> Result<(), Box<dyn Error>> {
|
pub fn run_command() -> Result<(), Box<dyn Error>> {
|
||||||
|
@ -110,7 +110,7 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
|
||||||
print_good( "exported palettes.pal" );
|
print_good( "exported palettes.pal" );
|
||||||
|
|
||||||
let mut nametables_bin = File::create( format!( "{}nametables.map", output_directory ) )?;
|
let mut nametables_bin = File::create( format!( "{}nametables.map", output_directory ) )?;
|
||||||
nametables_bin.write_all( &get_tilemap( &tiled_file )? )?;
|
nametables_bin.write_all( &get_metatile_maps( &tiled_file )? )?;
|
||||||
print_good( "exported nametables.map" );
|
print_good( "exported nametables.map" );
|
||||||
|
|
||||||
let mut nametables_bin = File::create( format!( "{}collision.lvc", output_directory ) )?;
|
let mut nametables_bin = File::create( format!( "{}collision.lvc", output_directory ) )?;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{error::Error, fs::read_to_string, path::Path, borrow::Cow};
|
use std::{error::Error, fs::read_to_string, path::Path, borrow::Cow, collections::HashSet};
|
||||||
use euclid::default::Rect;
|
use euclid::default::Rect;
|
||||||
use image::{DynamicImage, GenericImageView};
|
use image::{DynamicImage, GenericImageView};
|
||||||
use linked_hash_map::LinkedHashMap;
|
use linked_hash_map::LinkedHashMap;
|
||||||
|
@ -8,6 +8,7 @@ use crate::reskit::{utility::print_warning, cli::settings::TileOrder};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TiledTilemap {
|
pub struct TiledTilemap {
|
||||||
pub tileset: Vec<TiledTileset>,
|
pub tileset: Vec<TiledTileset>,
|
||||||
|
pub metatiles: Vec<Metatile>,
|
||||||
pub layers: Vec<Layer>,
|
pub layers: Vec<Layer>,
|
||||||
pub objects: Vec<Object>,
|
pub objects: Vec<Object>,
|
||||||
pub collision: Vec<Rect<u16>>,
|
pub collision: Vec<Rect<u16>>,
|
||||||
|
@ -15,6 +16,15 @@ pub struct TiledTilemap {
|
||||||
pub height: usize
|
pub height: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Metatile {
|
||||||
|
pub id: u32,
|
||||||
|
pub source: String,
|
||||||
|
pub width: u16,
|
||||||
|
pub height: u16,
|
||||||
|
pub tiles: Vec<u32>
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
@ -32,7 +42,7 @@ pub struct SpriteMetadata {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TiledTileset {
|
pub struct TiledTileset {
|
||||||
pub first_gid: usize,
|
pub source: String,
|
||||||
pub image: DynamicImage,
|
pub image: DynamicImage,
|
||||||
pub palettes: Vec<Option<u8>>,
|
pub palettes: Vec<Option<u8>>,
|
||||||
pub tile_order: TileOrder,
|
pub tile_order: TileOrder,
|
||||||
|
@ -47,7 +57,7 @@ pub enum SystemPlane {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Layer {
|
pub enum Layer {
|
||||||
Tile {
|
Metatile {
|
||||||
system_plane: SystemPlane,
|
system_plane: SystemPlane,
|
||||||
tiles: Vec<u32>
|
tiles: Vec<u32>
|
||||||
}
|
}
|
||||||
|
@ -95,11 +105,11 @@ fn get_layer( layer: Node, map_width: usize, map_height: usize ) -> Result<Optio
|
||||||
let tiles: Vec<u32> = data.into_iter().map( | string | Ok( string.trim().parse()? ) ).collect::< Result< Vec<u32>, Box<dyn Error> > >()?;
|
let tiles: Vec<u32> = data.into_iter().map( | string | Ok( string.trim().parse()? ) ).collect::< Result< Vec<u32>, Box<dyn Error> > >()?;
|
||||||
|
|
||||||
match layer_type.to_lowercase().as_str() {
|
match layer_type.to_lowercase().as_str() {
|
||||||
"a" => Ok( Some( Layer::Tile {
|
"a" => Ok( Some( Layer::Metatile {
|
||||||
system_plane: SystemPlane::MdPlaneA,
|
system_plane: SystemPlane::MdPlaneA,
|
||||||
tiles
|
tiles
|
||||||
} ) ),
|
} ) ),
|
||||||
"b" => Ok( Some( Layer::Tile {
|
"b" => Ok( Some( Layer::Metatile {
|
||||||
system_plane: SystemPlane::MdPlaneB,
|
system_plane: SystemPlane::MdPlaneB,
|
||||||
tiles
|
tiles
|
||||||
} ) ),
|
} ) ),
|
||||||
|
@ -118,11 +128,51 @@ fn get_layer( layer: Node, map_width: usize, map_height: usize ) -> Result<Optio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tiles( tileset: Node, first_gid: usize, working_directory: &str ) -> Result<TiledTileset, Box<dyn Error>> {
|
fn get_tiles( tileset: Node, seen_sources: &mut HashSet<String>, working_directory: &str ) -> Result<Option<TiledTileset>, Box<dyn Error>> {
|
||||||
// Get the image for the tileset
|
// Get the image for the tileset and its source
|
||||||
let image = tileset.descendants().find( | node | node.tag_name() == "image".into() ).ok_or( "invalid file: no image object" )?;
|
let image = tileset.descendants().find( | node | node.tag_name() == "image".into() ).ok_or( "invalid file: no image object" )?;
|
||||||
let image_path = format!( "{}/{}", working_directory, image.attribute( "source" ).ok_or( "invalid file: no source attribute on image" )? );
|
let source = image.attribute( "source" ).ok_or( "invalid file: no source attribute on image" )?.to_string();
|
||||||
let image = image::open( image_path )?;
|
|
||||||
|
// If the file path ends in .tmx, it's a metatile definition. We need to hop to the .tmx file in source, then redefine layer
|
||||||
|
// to the single tileset in that metatile .tmx.
|
||||||
|
let full_path = format!( "{}/{}", working_directory, source );
|
||||||
|
let formatted_path = Path::new( &full_path );
|
||||||
|
if let Some( extension ) = formatted_path.extension() {
|
||||||
|
let extension = extension.to_string_lossy();
|
||||||
|
if extension == "tmx" {
|
||||||
|
// Case where source is a nested tileset. Open the .tmx file
|
||||||
|
let file = read_to_string( full_path.clone() )?;
|
||||||
|
let tmx_document = roxmltree::Document::parse( &file )?;
|
||||||
|
|
||||||
|
// Obtain the single (first) <tileset>
|
||||||
|
let tileset = tmx_document.descendants().find( | node | node.tag_name() == "tileset".into() ).ok_or( "invalid file: metatile has no tileset" )?;
|
||||||
|
let tileset_source = tileset.attribute( "source" ).ok_or( "invalid file: metatile tileset has no source" )?.to_string();
|
||||||
|
|
||||||
|
// tileset_source should already point to the _actual_ source we need. Was this tileset source already seen? If so, bail
|
||||||
|
if seen_sources.contains( &tileset_source ) {
|
||||||
|
return Ok( None )
|
||||||
|
} else {
|
||||||
|
seen_sources.insert( tileset_source.clone() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load nested tileset into roxmltree document
|
||||||
|
let file = read_to_string( format!( "{}/{}", working_directory, tileset_source ) )?;
|
||||||
|
let nested_tileset = roxmltree::Document::parse( &file )?;
|
||||||
|
let tileset = nested_tileset.descendants().find( | node | node.tag_name() == "tileset".into() ).ok_or( "invalid file: metatile tileset has no tileset" )?;
|
||||||
|
|
||||||
|
// Return nested tileset from tmx file
|
||||||
|
let result = get_tiles( tileset, seen_sources, working_directory )?;
|
||||||
|
if let Some( mut result ) = result {
|
||||||
|
result.source = tileset_source.to_owned();
|
||||||
|
return Ok( Some( result ) )
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok( result )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case where source is an actual image
|
||||||
|
let image = image::open( full_path )?;
|
||||||
|
|
||||||
// Image must be a multiple of 8 (--system md)
|
// Image must be a multiple of 8 (--system md)
|
||||||
if image.width() % 8 != 0 { return Err( "invalid file: tileset width not multiple of 8" )? }
|
if image.width() % 8 != 0 { return Err( "invalid file: tileset width not multiple of 8" )? }
|
||||||
|
@ -206,7 +256,56 @@ fn get_tiles( tileset: Node, first_gid: usize, working_directory: &str ) -> Resu
|
||||||
TileOrder::Tile
|
TileOrder::Tile
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok( TiledTileset { first_gid, image, palettes, tile_order, sprite_metadata } )
|
Ok( Some( TiledTileset { source: format!( "" ), image, palettes, tile_order, sprite_metadata } ) )
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_metatile( id: u32, tileset: Node, working_directory: &str ) -> Result<Option<Metatile>, Box<dyn Error>> {
|
||||||
|
// Get the image for the tileset and its source
|
||||||
|
let image = tileset.descendants().find( | node | node.tag_name() == "image".into() ).ok_or( "invalid file: no image object" )?;
|
||||||
|
let source = image.attribute( "source" ).ok_or( "invalid file: no source attribute on image" )?.to_string();
|
||||||
|
let full_path = format!( "{}/{}", working_directory, source );
|
||||||
|
let formatted_path = Path::new( &full_path );
|
||||||
|
if let Some( extension ) = formatted_path.extension() {
|
||||||
|
let extension = extension.to_string_lossy();
|
||||||
|
if extension == "tmx" {
|
||||||
|
// Case where source is a nested tileset. Open the .tmx file
|
||||||
|
let file = read_to_string( full_path.clone() )?;
|
||||||
|
let tmx_document = roxmltree::Document::parse( &file )?;
|
||||||
|
|
||||||
|
// Get width and height from <map> item
|
||||||
|
let map_item = tmx_document.descendants().find( | node | node.tag_name() == "map".into() ).ok_or( "invalid file: no map element in metatile" )?;
|
||||||
|
let width: u16 = map_item.attribute( "width" ).ok_or( "invalid file: map of metatile has no width attribute" )?.parse()?;
|
||||||
|
let height: u16 = map_item.attribute( "height" ).ok_or( "invalid file: map of metatile has no height attribute" )?.parse()?;
|
||||||
|
|
||||||
|
// Obtain the single (first) <tileset>
|
||||||
|
let tileset_count = tmx_document.descendants().filter( | node | node.tag_name() == "tileset".into() ).count();
|
||||||
|
if tileset_count > 1 {
|
||||||
|
return Err( "invalid file: only a single tileset is valid for a metatile file" )?
|
||||||
|
}
|
||||||
|
let tileset = tmx_document.descendants().find( | node | node.tag_name() == "tileset".into() ).ok_or( "invalid file: metatile has no tileset" )?;
|
||||||
|
let source = tileset.attribute( "source" ).ok_or( "invalid file: metatile tileset has no source" )?.to_string();
|
||||||
|
|
||||||
|
// Create metatile object here
|
||||||
|
let data = tmx_document.descendants().find( | node | node.tag_name() == "data".into() ).ok_or( "invalid file: metatile has no data" )?;
|
||||||
|
if data.attribute( "encoding" ) != Some( "csv" ) {
|
||||||
|
return Err( "invalid file: metatile data not in csv format" )?
|
||||||
|
}
|
||||||
|
let data = data.text().ok_or( "invalid file: data node has no text" )?;
|
||||||
|
let values: Vec<&str> = data.split( "," ).collect();
|
||||||
|
// Subtract 1 to each value to account for firstgid. This is a sane assumption because a valid metatile file only contains a single tileset.
|
||||||
|
let tiles: Vec<u32> = values.into_iter().map( | string | {
|
||||||
|
let val = string.trim().parse::<u32>().expect( "fatal: non-u32 value in metatile" );
|
||||||
|
if val == 0 {
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
val - 1
|
||||||
|
}
|
||||||
|
} ).collect();
|
||||||
|
return Ok( Some( Metatile { id, source, width, height, tiles } ) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok( None )
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_objs( node: &Node, object_fields: &Vec<&str> ) -> Result<Vec<Object>, Box<dyn Error>> {
|
pub fn get_objs( node: &Node, object_fields: &Vec<&str> ) -> Result<Vec<Object>, Box<dyn Error>> {
|
||||||
|
@ -306,25 +405,34 @@ pub fn get_tiled_tilemap( path: &str, object_fields: &Vec<&str> ) -> Result<Tile
|
||||||
|
|
||||||
// Build tilesets (current version assumes one tileset per level)
|
// Build tilesets (current version assumes one tileset per level)
|
||||||
let mut tilesets: Vec<TiledTileset> = vec![];
|
let mut tilesets: Vec<TiledTileset> = vec![];
|
||||||
|
let mut metatiles: Vec<Metatile> = vec![];
|
||||||
|
let mut seen_sources: HashSet<String> = HashSet::new();
|
||||||
for tileset in map.descendants().filter( | node | node.tag_name() == "tileset".into() ) {
|
for tileset in map.descendants().filter( | node | node.tag_name() == "tileset".into() ) {
|
||||||
|
let tileset_first_gid = tileset.attribute( "firstgid" ).ok_or( "invalid file: no tileset firstgid" )?;
|
||||||
let tileset_source_path = tileset.attribute( "source" ).ok_or( "invalid file: no tileset source" )?;
|
let tileset_source_path = tileset.attribute( "source" ).ok_or( "invalid file: no tileset source" )?;
|
||||||
let tileset_file = read_to_string( format!( "{}/{}", working_directory, tileset_source_path ) )?;
|
let tileset_file = read_to_string( format!( "{}/{}", working_directory, tileset_source_path ) )?;
|
||||||
let tileset_document = roxmltree::Document::parse( &tileset_file )?;
|
let tileset_document = roxmltree::Document::parse( &tileset_file )?;
|
||||||
let first_gid = tileset.attribute( "firstgid" ).ok_or( "invalid file: no firstgid attribute" )?.parse()?;
|
|
||||||
let tileset = tileset_document.descendants().find( | node | node.tag_name() == "tileset".into() ).ok_or( "invalid file: no tileset origin object" )?;
|
let tileset = tileset_document.descendants().find( | node | node.tag_name() == "tileset".into() ).ok_or( "invalid file: no tileset origin object" )?;
|
||||||
|
|
||||||
// Validate referenced tileset dimensions match map dimensions
|
// Tilesets may instead contain metatiles
|
||||||
// E.g. if your .tmx specifies 16x16, the acompanying .tsx file should agree
|
// First, load tilesets instead of metatiles
|
||||||
let ( tsx_tile_width, tsx_tile_height ): ( usize, usize ) = (
|
// If tileset_source_path was seen, don't proceed
|
||||||
tileset.attribute( "tilewidth" ).ok_or( "invalid file: no tilewidth attribute" )?.parse()?,
|
if !seen_sources.contains( tileset_source_path ) {
|
||||||
tileset.attribute( "tileheight" ).ok_or( "invalid file: no tileheight attribute" )?.parse()?
|
seen_sources.insert( tileset_source_path.to_owned() );
|
||||||
);
|
|
||||||
if tsx_tile_width != tile_width || tsx_tile_height != tile_height {
|
if let Some( mut tiles ) = get_tiles( tileset.clone(), &mut seen_sources, &working_directory )? {
|
||||||
return Err( "invalid file: referenced tilemap doesn't have same per-tile width and height of parent tilemap" )?
|
if tiles.source == "" {
|
||||||
|
tiles.source = tileset_source_path.to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
tilesets.push( get_tiles( tileset, first_gid, &working_directory )? );
|
tilesets.push( tiles );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, check for metatiles
|
||||||
|
if let Some( metatile ) = get_metatile( tileset_first_gid.parse()?, tileset, &working_directory )? {
|
||||||
|
metatiles.push( metatile );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tileset = tilesets;
|
let tileset = tilesets;
|
||||||
|
@ -381,16 +489,7 @@ pub fn get_tiled_tilemap( path: &str, object_fields: &Vec<&str> ) -> Result<Tile
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(
|
Ok( TiledTilemap { tileset, metatiles, layers, objects, collision, width, height } )
|
||||||
TiledTilemap {
|
|
||||||
tileset,
|
|
||||||
layers,
|
|
||||||
objects,
|
|
||||||
collision,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Err( "invalid file: this does not appear to be valid Tiled .tmx file" )?
|
Err( "invalid file: this does not appear to be valid Tiled .tmx file" )?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{error::Error, convert::TryInto, collections::HashMap};
|
use std::{error::Error, collections::HashMap};
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use crate::reskit::{tileset::image_to_tiles, utility::symbol_to_pascal, cli::settings::TileOrder};
|
use crate::reskit::{tileset::image_to_tiles, utility::symbol_to_pascal, cli::settings::TileOrder};
|
||||||
use super::converter::{TiledTilemap, Layer, SystemPlane, TiledTileset};
|
use super::converter::{TiledTilemap, Layer, SystemPlane, TiledTileset};
|
||||||
|
@ -109,90 +109,147 @@ pub fn get_tiles( tilemap: &TiledTilemap ) -> Result<(Vec<u8>, Vec<u8>), Box<dyn
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output the .map file defining the hardware tilemap for the given Tiled Tilemap.
|
* Get the .map file containing metatile definitions, as well as B and A metatile-based maps.
|
||||||
* In `--system md`, this outputs tilemap B and then tilemap A
|
*
|
||||||
|
* Format:
|
||||||
|
* <<< HEADER >>>
|
||||||
|
* 2 bytes: The offset to the instances table
|
||||||
|
* (n) 2 byte offsets: For the number of defined metatiles, 16-bit offsets into the file
|
||||||
|
* pointing to the metatile definition. Index into this table = the metatile ID.
|
||||||
|
*
|
||||||
|
* <<< DEFINITIONS >>>
|
||||||
|
* For each metatile definition:
|
||||||
|
* 2 bytes: Width of the metatile, in 16-bit words.
|
||||||
|
* 2 bytes: Height of the metatile, in 16-bit words.
|
||||||
|
* (n) bytes: The nametable definition for the metatile. Stamp this into vram on each layer.
|
||||||
|
*
|
||||||
|
* <<< INSTANCES >>>
|
||||||
|
* For B, then A layers:
|
||||||
|
* 2 bytes: Number of metatile instances in this layer.
|
||||||
|
* For each metatile instance:
|
||||||
|
* 2 bytes: Metatile ID.
|
||||||
|
* 2 bytes: X coordinate (world, divide by 8 to get tile coordinate).
|
||||||
|
* 2 bytes: Y coordinate (world, divide by 8 to get tile coordinate).
|
||||||
*/
|
*/
|
||||||
pub fn get_tilemap( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
pub fn get_metatile_maps( tilemap: &TiledTilemap ) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
let mut all_palettes: Vec<Option<u8>> = vec![];
|
// Assemble header of offsets (as we go)
|
||||||
for tileset in &tilemap.tileset {
|
let header_offset = ( tilemap.metatiles.len() * 2 ) + 2;
|
||||||
all_palettes.extend( tileset.palettes.iter() );
|
let mut header: Vec<u8> = vec![];
|
||||||
|
|
||||||
|
// Assemble definitions
|
||||||
|
let mut definitions: Vec<u8> = vec![];
|
||||||
|
for metatile in &tilemap.metatiles {
|
||||||
|
let mut subdefinitions: Vec<u8> = vec![];
|
||||||
|
subdefinitions.extend( metatile.width.to_be_bytes() );
|
||||||
|
subdefinitions.extend( metatile.height.to_be_bytes() );
|
||||||
|
|
||||||
|
let ( tilemap_index, palettes ) = metatile_tilemap_index( &metatile.source, &tilemap.tileset )?;
|
||||||
|
for tile in &metatile.tiles {
|
||||||
|
let palette = {
|
||||||
|
if *tile == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let palette = palettes.get( *tile as usize ).ok_or( "internal error: no correlation between tile index and palette index" )?;
|
||||||
|
if let Ok( palette ) = palette.ok_or( "" ) {
|
||||||
|
palette
|
||||||
|
} else {
|
||||||
|
println!( "{:?}", palettes );
|
||||||
|
return Err( format!( "invalid file: tile \"{}\" in metatile tilemap does not name reskit-palette attribute", tile ) )?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let nametable_entry: u16 = ( ( palette as u16 ) << 13 ) | ( ( tile + tilemap_index as u32 ) as u16 );
|
||||||
|
subdefinitions.extend( nametable_entry.to_be_bytes() );
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer_b: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Tile { system_plane: SystemPlane::MdPlaneB, tiles: _ } ) );
|
let this_offset = header_offset + definitions.len();
|
||||||
let layer_a: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Tile { system_plane: SystemPlane::MdPlaneA, tiles: _ } ) );
|
header.extend( ( this_offset as u16 ).to_be_bytes() );
|
||||||
|
|
||||||
// Each entry in a `--system md` tilemap is 16 bits
|
definitions.extend( subdefinitions );
|
||||||
let mut total_nametable: Vec<u16> = Vec::new();
|
}
|
||||||
|
|
||||||
let mut nametable: Vec<u16> = vec![ 0; tilemap.width * tilemap.height ];
|
// Assemble instances
|
||||||
|
let mut instances: Vec<u8> = vec![];
|
||||||
|
|
||||||
|
let layer_b: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Metatile { system_plane: SystemPlane::MdPlaneB, tiles: _ } ) );
|
||||||
if let Some( layer_b ) = layer_b {
|
if let Some( layer_b ) = layer_b {
|
||||||
let layer_b = match layer_b {
|
let tiles = match layer_b { Layer::Metatile { system_plane: _, tiles } => tiles };
|
||||||
Layer::Tile { system_plane: _, tiles } => tiles,
|
let mut subdefinitions: Vec<u8> = vec![];
|
||||||
_ => return Err( "internal error: invalid object type" )?
|
|
||||||
};
|
|
||||||
|
|
||||||
for y in 0..tilemap.height {
|
for tile_y in 0..tilemap.height {
|
||||||
for x in 0..tilemap.width {
|
for tile_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 item_at = tiles.get( ( tile_y * tilemap.width ) + tile_x ).ok_or( "internal error: tilemap does not correlate to width/height" )?;
|
||||||
let target_tile: u16 = target_tile.try_into()?;
|
if *item_at != 0 {
|
||||||
if target_tile > 0 {
|
// What index-ID was that?
|
||||||
let source_tile = target_tile - 1;
|
let index_id = tilemap.metatiles.iter().position( | metatile | metatile.id == *item_at ).ok_or( "invalid file: metatile id not found" )?;
|
||||||
|
|
||||||
// From the starting point of x, y "stamp" the target tile's indices
|
// Write the data to subdefinitions
|
||||||
let nametable_entry = target_tile;
|
subdefinitions.extend( ( index_id as u16 ).to_be_bytes() );
|
||||||
let selected_pal = all_palettes[ source_tile as usize ];
|
subdefinitions.extend( ( ( tile_x * 8 ) as u16 ).to_be_bytes() );
|
||||||
if let Some( selected_pal ) = selected_pal {
|
subdefinitions.extend( ( ( tile_y * 8 ) as u16 ).to_be_bytes() );
|
||||||
nametable[ ( y * tilemap.width ) + x ] = ( ( selected_pal as u16 ) << 13 ) | nametable_entry;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instances.extend( ( ( subdefinitions.len() / 6 ) as u16 ).to_be_bytes() );
|
||||||
|
instances.extend( subdefinitions );
|
||||||
} else {
|
} else {
|
||||||
return Err( format!( "invalid setting: tile {} in tileset has no defined palette", source_tile ) )?
|
instances.extend( ( 0 as u16 ).to_be_bytes() );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total_nametable.extend( nametable.iter() );
|
|
||||||
|
|
||||||
// Just do the same for layer a
|
let layer_a: Option<&Layer> = tilemap.layers.iter().find( | layer | matches!( layer, Layer::Metatile { system_plane: SystemPlane::MdPlaneA, tiles: _ } ) );
|
||||||
// Copy pasted because i'm lazy and tired
|
|
||||||
let mut nametable: Vec<u16> = vec![ 0; tilemap.width * tilemap.height ];
|
|
||||||
if let Some( layer_a ) = layer_a {
|
if let Some( layer_a ) = layer_a {
|
||||||
let layer_a = match layer_a {
|
let tiles = match layer_a { Layer::Metatile { system_plane: _, tiles } => tiles };
|
||||||
Layer::Tile { system_plane: _, tiles } => tiles,
|
let mut subdefinitions: Vec<u8> = vec![];
|
||||||
_ => return Err( "internal error: invalid object type" )?
|
|
||||||
};
|
|
||||||
|
|
||||||
for y in 0..tilemap.height {
|
for tile_y in 0..tilemap.height {
|
||||||
for x in 0..tilemap.width {
|
for tile_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 item_at = tiles.get( ( tile_y * tilemap.width ) + tile_x ).ok_or( "internal error: tilemap does not correlate to width/height" )?;
|
||||||
let target_tile: u16 = target_tile.try_into()?;
|
if *item_at != 0 {
|
||||||
if target_tile > 0 {
|
// What index-ID was that?
|
||||||
let source_tile = target_tile - 1;
|
let index_id = tilemap.metatiles.iter().position( | metatile | metatile.id == *item_at ).ok_or( "invalid file: metatile id not found" )?;
|
||||||
|
|
||||||
// From the starting point of x, y "stamp" the target tile's indices
|
// Write the data to subdefinitions
|
||||||
let nametable_entry = target_tile;
|
subdefinitions.extend( ( index_id as u16 ).to_be_bytes() );
|
||||||
let selected_pal = all_palettes[ source_tile as usize ];
|
subdefinitions.extend( ( ( tile_x * 8 ) as u16 ).to_be_bytes() );
|
||||||
if let Some( selected_pal ) = selected_pal {
|
subdefinitions.extend( ( ( tile_y * 8 ) as u16 ).to_be_bytes() );
|
||||||
nametable[ ( y * tilemap.width ) + x ] = ( ( selected_pal as u16 ) << 13 ) | nametable_entry;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instances.extend( ( ( subdefinitions.len() / 6 ) as u16 ).to_be_bytes() );
|
||||||
|
instances.extend( subdefinitions );
|
||||||
} else {
|
} else {
|
||||||
return Err( format!( "invalid setting: tile {} in tileset has no defined palette", source_tile ) )?
|
instances.extend( ( 0 as u16 ).to_be_bytes() );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total_nametable.extend( nametable.iter() );
|
|
||||||
|
|
||||||
// Convert the u16's to a series of u8 data
|
// Write the final result
|
||||||
let mut result: Vec<u8> = Vec::new();
|
let mut result: Vec<u8> = vec![];
|
||||||
for i in 0..total_nametable.len() {
|
let offset_to_instances = header_offset + definitions.len();
|
||||||
let bytes = total_nametable[ i ].to_be_bytes();
|
result.extend( ( offset_to_instances as u16 ).to_be_bytes() );
|
||||||
for i in 0..2 {
|
result.extend( header );
|
||||||
result.push( bytes[ i ] );
|
result.extend( definitions );
|
||||||
}
|
result.extend( instances );
|
||||||
}
|
|
||||||
Ok( result )
|
Ok( result )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn metatile_tilemap_index( target_source: &str, tilesets: &Vec<TiledTileset> ) -> Result<(usize, Vec<Option<u8>>), Box<dyn Error>> {
|
||||||
|
// Everything starts at 1 for the blank buffer tile
|
||||||
|
let mut index: usize = 1;
|
||||||
|
|
||||||
|
for tileset in tilesets {
|
||||||
|
if tileset.source == target_source {
|
||||||
|
return Ok( ( index, tileset.palettes.clone() ) )
|
||||||
|
} else {
|
||||||
|
index += ( ( tileset.image.width() / 8 ) * ( tileset.image.height() / 8 ) ) as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err( format!( "internal error: could not find metatile with target source \"{}\"", target_source ) )?
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the .lvc collision map (u8 sized for the map dimensions, collision areas are either 0 or 1)
|
* Get the .lvc collision map (u8 sized for the map dimensions, collision areas are either 0 or 1)
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue