types.rs 17.05 KiB
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::ops::{Deref, DerefMut};
use std::path::Path;
use bevy::math::{IVec2, Rect, UVec2, Vec2};
use bevy::prelude::Event;
use num_traits::AsPrimitive;
use quadtree_rs::area::AreaBuilder;
use quadtree_rs::point::Point;
use quadtree_rs::Quadtree;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Number, Value};
use crate::ldtk::{EntityInstance, FieldInstance, LayerInstance, Level, TileInstance};
use crate::system::Indexer;
use crate::{get_ldtk_tile_scale, px_to_grid, MapQuery};
#[derive(Default, Clone, Debug, Ord, PartialOrd, PartialEq, Eq, Event)]
pub struct LevelDataUpdated(pub String);
pub struct TileRef<'a> {
pub tile: &'a TileInstance,
}
#[repr(C)]
#[derive(Serialize, Deserialize, 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(Serialize, Deserialize, 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 type ColliderQuadtree = Quadtree<i64, LocatedEntity>;
pub struct LdtkLevel {
level: Level,
properties: HashMap<String, Value>,
processed_layers: Vec<LdtkLayer>,
colliders: ColliderQuadtree,
}
impl LdtkLevel {
pub fn width(&self) -> f32 {
self.level.px_wid as f32
}
pub fn width_i(&self) -> i64 {
self.level.px_wid
}
pub fn height(&self) -> f32 {
self.level.px_hei as f32
}
pub fn height_i(&self) -> i64 {
self.level.px_hei
}
pub fn level_ref(&self) -> &Level {
&self.level
}
pub fn level_ref_mut(&mut self) -> &mut Level {
&mut self.level
}
pub fn colliders_ref(&self) -> &ColliderQuadtree {
&self.colliders
}
pub fn colliders_ref_mut(&mut self) -> &mut ColliderQuadtree {
&mut self.colliders
}
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 properties_ref(&self) -> &HashMap<String, Value> {
&self.properties
}
pub fn properties_mut(&mut self) -> &mut HashMap<String, Value> {
&mut self.properties
}
pub fn property(&self, name: impl ToString) -> Option<&Value> {
self.properties.get(&name.to_string())
}
pub fn property_or_null(&self, name: impl ToString) -> &Value {
self.property(name).unwrap_or(&Value::Null)
}
pub fn get_indexer(&self) -> Indexer {
Indexer::new(px_to_grid(self.level.px_wid), px_to_grid(self.level.px_hei))
}
}
#[derive(Debug, Clone, Default)]
pub struct LocatedEntity {
pub position: IVec2,
pub size: IVec2,
pub properties: HashMap<String, Value>,
pub collides: bool,
}
impl From<Level> for LdtkLevel {
fn from(mut value: Level) -> Self {
let layers = value.layer_instances.take();
let mut properties = HashMap::with_capacity(value.field_instances.len());
let fields = std::mem::take(&mut value.field_instances);
for field in fields {
properties.insert(field.identifier, field.value.unwrap_or(Value::Null));
}
let _level_width = value.px_wid;
let level_height = value.px_hei;
let mut level = Self {
colliders: Quadtree::new(0),
processed_layers: layers
.unwrap_or_default()
.into_iter()
.rev()
.enumerate()
.map(|(idx, inst)| LdtkLayer::new(idx, inst))
.collect(),
level: value,
properties,
};
let mut collider_quads = Quadtree::<i64, LocatedEntity>::new(32);
for entity in MapQuery::get_entities_of(&level) {
if entity.tags.contains(&String::from("Collider")) {
let mut properties = HashMap::with_capacity(entity.field_instances.len());
for field in entity.field_instances.iter() {
properties.insert(
field.identifier.clone(),
field.value.as_ref().unwrap_or(&Value::Null).clone(),
);
}
let _width = entity.width;
let height = entity.height;
let _left_x = entity.px[0];
let _bottom_y = level_height - (entity.px[1] + height);
let size = IVec2::new(entity.width as i32, entity.height as i32);
let position = IVec2::new(level.level.px_wid as i32, level.level.px_hei as i32)
- (IVec2::new(entity.px[0] as i32, entity.px[1] as i32) + size);
let entity = LocatedEntity {
position,
size,
properties,
collides: true,
};
if let Ok(point) = AreaBuilder::default()
.anchor(Point::from((position.x as i64, position.y as i64)))
.dimensions((size.x as i64, size.y as i64))
.build()
{
collider_quads.insert(point, entity);
}
}
}
level.colliders = collider_quads;
level
}
}
pub struct LdtkLayer {
pub level: usize,
layer: LayerInstance,
position_lookup: HashMap<SpatialIndex, TileInstance>,
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.clone());
}
Self {
level: index,
indexer,
layer,
position_lookup,
}
}
pub fn name(&self) -> &String {
&self.layer.identifier
}
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)
})
}
pub fn layer_ref(&self) -> &LayerInstance {
&self.layer
}
pub fn layer_ref_mut(&mut self) -> &mut LayerInstance {
&mut self.layer
}
}
impl AsRef<LayerInstance> for LdtkLayer {
fn as_ref(&self) -> &LayerInstance {
&self.layer
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WorldTile {
gid: usize,
rotation: TileFlip,
}
impl From<&TileInstance> for WorldTile {
fn from(value: &TileInstance) -> Self {
Self {
gid: value.t.max(0) as usize,
rotation: match value.f {
1 => TileFlip::Horizontal,
2 => TileFlip::Vertical,
3 => TileFlip::Both,
_ => TileFlip::None,
},
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum EntityPosition {
Point { position: IVec2 },
Zone { position: IVec2, size: IVec2 },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WorldEntity {
entity_type: String,
position: Rect,
properties: Properties,
tags: Vec<String>,
colour: String,
}
impl From<&EntityInstance> for WorldEntity {
fn from(value: &EntityInstance) -> Self {
let x = value.px[0];
let y = value.px[1];
Self {
entity_type: value.identifier.clone(),
position: Rect::from_corners(
Vec2::new(x as f32, y as f32),
Vec2::new((x + value.width) as f32, (y + value.height) as f32),
),
properties: Properties::from(&value.field_instances),
tags: value.tags.clone(),
colour: value.smart_color.clone(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Properties(HashMap<String, Value>);
impl Deref for Properties {
type Target = HashMap<String, Value>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Properties {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<HashMap<String, Value>> for Properties {
fn from(value: HashMap<String, Value>) -> Self {
Self(value)
}
}
impl From<Vec<FieldInstance>> for Properties {
fn from(value: Vec<FieldInstance>) -> Self {
(&value).into()
}
}
impl From<&Vec<FieldInstance>> for Properties {
fn from(value: &Vec<FieldInstance>) -> Self {
value
.iter()
.map(|field| {
(
field.identifier.clone(),
field.value.clone().unwrap_or(Value::Null),
)
})
.collect::<HashMap<String, Value>>()
.into()
}
}
fn convert_map_types(map: &Map<String, Value>) -> HashMap<String, Value> {
map.iter()
.map(|(name, value)| (name.clone(), value.clone()))
.collect()
}
impl Properties {
pub fn as_string(&self, name: impl ToString) -> Option<String> {
self.get(&name.to_string()).and_then(|value| match value {
Value::String(value) => Some(value.to_string()),
Value::Bool(value) => Some(format!("{}", value)),
Value::Number(value) => Some(format!("{}", value)),
_ => None,
})
}
pub fn as_number_cast<T>(&self, name: impl ToString) -> Option<T>
where
T: 'static + Copy,
f64: AsPrimitive<T>,
{
self.get(&name.to_string()).and_then(|value| match value {
Value::Number(number) => {
let num = number.as_f64();
num.map(|val| val.as_())
}
_ => None,
})
}
pub fn as_bool(&self, name: impl ToString) -> Option<bool> {
self.get(&name.to_string()).and_then(|value| match value {
Value::Bool(value) => Some(*value),
_ => None,
})
}
pub fn as_vec(&self, name: impl ToString) -> Option<&Vec<Value>> {
self.get(&name.to_string()).and_then(|value| match value {
Value::Array(value) => Some(value),
_ => None,
})
}
pub fn as_vec_owned(&self, name: impl ToString) -> Option<Vec<Value>> {
self.get(&name.to_string()).and_then(|value| match value {
Value::Array(value) => Some(value.clone()),
_ => None,
})
}
pub fn as_map(&self, name: impl ToString) -> Option<&Map<String, Value>> {
self.get(&name.to_string()).and_then(|value| match value {
Value::Object(value) => Some(value),
_ => None,
})
}
pub fn as_map_owned(&self, name: impl ToString) -> Option<Map<String, Value>> {
self.get(&name.to_string()).and_then(|value| match value {
Value::Object(value) => Some(value.clone()),
_ => None,
})
}
pub fn matches(&self, name: impl ToString, value: Value) -> bool {
match (self.0.get(&name.to_string()), value) {
(Some(Value::String(a)), Value::String(ref b)) => a == b,
(Some(Value::Number(a)), Value::Number(ref b)) => a == b,
(Some(Value::Bool(a)), Value::Bool(ref b)) => a == b,
(Some(Value::Array(a)), Value::Array(ref b)) => a == b,
(Some(Value::Object(a)), Value::Object(ref b)) => a == b,
(Some(Value::Null), Value::Null) => true,
_ => false,
}
}
pub fn is_null(&self, name: impl ToString) -> bool {
matches!(self.0.get(&name.to_string()), Some(Value::Null))
}
pub fn is_null_or_undefined(&self, name: impl ToString) -> bool {
matches!(self.0.get(&name.to_string()), None | Some(Value::Null))
}
pub fn is_null_or_falsy(&self, name: impl ToString) -> bool {
match self.0.get(&name.to_string()) {
None | Some(Value::Null) => true,
Some(Value::Bool(value)) => !*value,
Some(Value::String(value)) => value.is_empty(),
Some(Value::Number(num)) => num == &Number::from(0),
Some(Value::Array(value)) => value.is_empty(),
Some(Value::Object(value)) => value.is_empty(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum LayerType {
Entities(Vec<WorldEntity>),
GidTiles(HashMap<SpatialIndex, WorldTile>),
IntTiles(Vec<usize>),
}
fn infer_tileset_name(layer: &LayerInstance) -> Option<String> {
layer.tileset_rel_path.as_ref().and_then(|path| {
Path::new(path)
.file_stem()
.and_then(|stem| stem.to_str())
.map(String::from)
})
}
impl From<&LayerInstance> for LayerType {
fn from(value: &LayerInstance) -> Self {
match value.layer_instance_type.as_str() {
"IntGrid" => LayerType::IntTiles(
value
.int_grid_csv
.iter()
.map(|num| (*num).max(0) as usize)
.collect(),
),
"Entities" => LayerType::Entities(
value
.entity_instances
.iter()
.map(|entity| entity.into())
.collect(),
),
"Tiles" => LayerType::GidTiles(
value
.grid_tiles
.iter()
.map(|tile| (SpatialIndex(tile.px[0], tile.px[1]), tile.into()))
.collect(),
),
"AutoLayer" => LayerType::GidTiles(
value
.auto_layer_tiles
.iter()
.map(|tile| (SpatialIndex(tile.px[0], tile.px[1]), tile.into()))
.collect(),
),
_ => {
panic!("Invalid layer type {}", value.layer_instance_type.as_str());
}
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WorldLayer {
id: String,
depth: usize,
world_width: i64,
world_height: i64,
tile_width: i64,
tile_height: i64,
layer_data: LayerType,
tileset_name: Option<String>,
}
impl WorldLayer {
pub fn from_layer(depth: usize, layer: &LayerInstance) -> Self {
let tile_size = get_ldtk_tile_scale() as i64;
Self {
depth,
id: layer.identifier.clone(),
tileset_name: infer_tileset_name(layer),
world_width: layer.c_wid * tile_size,
world_height: layer.c_hei * tile_size,
tile_width: layer.c_wid,
tile_height: layer.c_hei,
layer_data: layer.into(),
}
}
pub fn has_tiles(&self) -> bool {
match &self.layer_data {
LayerType::GidTiles(map) => !map.is_empty(),
_ => false,
}
}
pub fn for_each_tile(&self, mut cb: impl FnMut(i64, i64, &WorldTile)) {
if let LayerType::GidTiles(map) = &self.layer_data {
map.iter().for_each(|(pos, tile)| {
cb(pos.0, pos.1, tile);
});
}
}
pub fn for_each_tile_mut(&mut self, mut cb: impl FnMut(i64, i64, &mut WorldTile)) {
if let LayerType::GidTiles(map) = &mut self.layer_data {
map.iter_mut().for_each(|(pos, tile)| {
cb(pos.0, pos.1, tile);
});
}
}
pub fn get_z_delta(&self) -> f32 {
(self.depth as f32) / 100.0
}
pub fn get_tile(
&self,
x: impl AsPrimitive<i32>,
y: impl AsPrimitive<i32>,
) -> Option<&WorldTile> {
match &self.layer_data {
LayerType::GidTiles(map) => map.get(&IVec2::from((x.as_(), y.as_())).into()),
_ => None,
}
}
pub fn get_tile_mut(
&mut self,
x: impl AsPrimitive<i32>,
y: impl AsPrimitive<i32>,
) -> Option<&mut WorldTile> {
match &mut self.layer_data {
LayerType::GidTiles(map) => map.get_mut(&IVec2::from((x.as_(), y.as_())).into()),
_ => None,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct WorldData {
layers: Vec<WorldLayer>,
properties: Properties,
meta: Properties,
}
impl From<&Level> for WorldData {
fn from(value: &Level) -> Self {
let mut properties: Properties = (&value.field_instances).into();
let meta: Properties = properties
.as_map("meta")
.map(convert_map_types)
.unwrap_or_default()
.into();
properties.remove("meta");
Self {
properties,
meta,
layers: value
.layer_instances
.as_ref()
.map(|insts| {
insts
.iter()
.enumerate()
.map(|(idx, layer)| WorldLayer::from_layer(idx, layer))
.collect()
})
.unwrap_or_default(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LayerSet(pub Vec<WorldLayer>);
trait AsWorldLayer {
fn as_world_layer(&self, depth: usize) -> WorldLayer;
}
// impl<T, V> From<V> for LayerSet
// where
// T: AsWorldLayer,
// V: Iterator<Item = T>,
// {
// fn from(value: V) -> Self {
// Self(value.enumerate().map(T::as_world_layer).collect())
// }
// }
// impl<T> From<T> for WorldData
// where
// T: Into<LayerSet>,
// {
// fn from(value: T) -> Self {
// let layers = value.into();
// WorldData {
// layers: layers.0,
// ..Default::default()
// }
// }
// }