Skip to content
Snippets Groups Projects
types.rs 4.76 KiB
Newer Older
use std::fmt::{Debug, Formatter};
use std::path::Path;

use bevy::math::{IVec2, UVec2};
use bevy::utils::HashMap;
use ldtk_rust::{LayerInstance, Level, TileInstance};
use num_traits::AsPrimitive;

use crate::utils::{Indexer, SerdeClone};
use crate::{get_ldtk_tile_scale, px_to_grid};

pub struct TileRef<'a> {
	pub tile: &'a TileInstance,
}

#[repr(C)]
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default, Debug)]
pub enum TileFlip {
	#[default]
	None = 0,
	Horizontal = 1,
	Vertical = 2,
	Both = 3,
}

impl TileFlip {
	pub fn is_horizontal(&self) -> bool {
		match self {
			Self::None | Self::Vertical => false,
			Self::Horizontal | Self::Both => true,
		}
	}
	pub fn is_vertical(&self) -> bool {
		match self {
			Self::None | Self::Horizontal => false,
			Self::Vertical | Self::Both => true,
		}
	}
}

impl<'a> TileRef<'a> {
	pub fn new(tile: &'a TileInstance) -> Self {
		TileRef { tile }
	}
	pub fn gid(&self) -> usize {
		(self.tile.src[0] * self.tile.src[1]).unsigned_abs() as usize
	}
	pub fn get_flip(&self) -> TileFlip {
		match self.tile.f {
			1 => TileFlip::Horizontal,
			2 => TileFlip::Vertical,
			3 => TileFlip::Both,
			_ => TileFlip::None,
		}
	}
}

impl<'a> Debug for TileRef<'a> {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("TileRef")
			.field("gid", &self.gid())
			.field("tile.t", &self.tile.t)
			.field("tile.d", &self.tile.d)
			.field("tile.px", &self.tile.px)
			.field("tile.src", &self.tile.src)
			.finish()
	}
}

#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Hash, Debug)]
pub struct SpatialIndex(i64, i64);
impl<A, B> From<(A, B)> for SpatialIndex
where
	A: AsPrimitive<i64>,
	B: AsPrimitive<i64>,
{
	fn from(value: (A, B)) -> Self {
		Self(value.0.as_(), value.1.as_())
	}
}
impl From<UVec2> for SpatialIndex {
	fn from(value: UVec2) -> Self {
		Self(value.x as i64, value.y as i64)
	}
}
impl From<IVec2> for SpatialIndex {
	fn from(value: IVec2) -> Self {
		Self(value.x as i64, value.y as i64)
	}
}

pub struct LdtkLevel {
	level: Level,
	processed_layers: Vec<LdtkLayer>,
}

impl LdtkLevel {
	pub fn level_ref(&self) -> &Level {
		&self.level
	}
	pub fn level_ref_mut(&mut self) -> &mut Level {
		&mut self.level
	}
	pub fn layers(&self) -> impl DoubleEndedIterator<Item = &LdtkLayer> {
		self.processed_layers.iter()
	}
	pub fn layers_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut LdtkLayer> {
		self.processed_layers.iter_mut()
	}
	pub fn get_indexer(&self) -> Indexer {
		Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei))
	}
}

impl From<Level> for LdtkLevel {
	fn from(mut value: Level) -> Self {
		let layers = value.layer_instances.take();

		Self {
			processed_layers: layers
				.unwrap_or_default()
				.into_iter()
				.enumerate()
				.map(|(idx, inst)| LdtkLayer::new(idx, inst))
				.collect(),
			level: value,
		}
	}
}

pub struct LdtkLayer {
	layer: LayerInstance,
	position_lookup: HashMap<SpatialIndex, TileInstance>,
	level: usize,
	indexer: Indexer,
}

impl LdtkLayer {
	pub fn new(index: usize, layer: LayerInstance) -> Self {
		let tile_list = layer.grid_tiles.iter().chain(&layer.auto_layer_tiles);
		let mut position_lookup =
			HashMap::with_capacity(layer.grid_tiles.len() + layer.auto_layer_tiles.len());

		let scale = get_ldtk_tile_scale() as i64;
		let indexer = Indexer::new(layer.c_wid, layer.c_hei);

		for tile in tile_list {
			let x = tile.px[0] / scale;
			let y = tile.px[1] / scale;

			position_lookup.insert((x, y).into(), tile.serde_clone());
		}

		Self {
			level: index,
			indexer,
			layer,
			position_lookup,
		}
	}

	pub fn indexer(&self) -> Indexer {
		self.indexer
	}

	pub fn has_tiles(&self) -> bool {
		!(self.layer.auto_layer_tiles.is_empty() && self.layer.grid_tiles.is_empty())
	}

	pub fn for_each_tile(&self, mut cb: impl FnMut(i64, i64, TileRef)) {
		self.position_lookup.iter().for_each(|(pos, tile)| {
			cb(pos.0, pos.1, TileRef::new(tile));
		});
	}

	pub fn get_z_delta(&self) -> f32 {
		(self.level as f32) / 100.0
	}

	pub fn get_tile(&self, pos: impl Into<SpatialIndex>) -> Option<TileRef> {
		self.position_lookup.get(&pos.into()).map(TileRef::new)
	}
	pub fn get_tile_at(
		&self,
		a: impl AsPrimitive<i64>,
		b: impl AsPrimitive<i64>,
	) -> Option<TileRef> {
		self.position_lookup
			.get(&SpatialIndex(a.as_(), b.as_()))
			.map(TileRef::new)
	}

	/// Returns the inferred name of the tileset used for this layer. This is assumed to be the
	/// name of the tileset file, without the preceding path segments or the file extension. Case
	/// remains unchanged
	pub fn infer_tileset_name(&self) -> Option<String> {
		self.layer.tileset_rel_path.as_ref().and_then(|path| {
			Path::new(path)
				.file_stem()
				.and_then(|stem| stem.to_str())
				.map(String::from)
		})
	}
}

impl AsRef<LayerInstance> for LdtkLayer {
	fn as_ref(&self) -> &LayerInstance {
		&self.layer
	}
}