map_query.rs 7.23 KiB
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
use std::path::Path;
use std::str::FromStr;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use ldtk_rust::{EntityInstance, LayerInstance, Level, TileInstance};
use num_traits::AsPrimitive;
// use crate::assets::level_index::LevelIndex;
use crate::assets::{LdtkProject, LevelIndex};
use crate::utils::{ActiveLevel, Indexer, SerdeClone};
use crate::{get_ldtk_tile_scale, px_to_grid};
#[derive(SystemParam)]
pub struct MapQuery<'w, 's> {
assets: Res<'w, Assets<LdtkProject>>,
active: Option<Res<'w, ActiveLevel>>,
index: Res<'w, LevelIndex>,
#[system_param(ignore)]
_e: PhantomData<&'s ()>,
}
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()
}
}
pub struct InstanceRef<'a> {
pub entity: &'a EntityInstance,
}
impl<'a> InstanceRef<'a> {
pub fn get_type(&self) -> &'a String {
&self.entity.identifier
}
pub fn try_get_typed_id<T: FromStr>(&self) -> Result<T, T::Err> {
T::from_str(self.get_type().as_str())
}
pub fn property(&self, name: impl ToString) -> serde_json::Value {
let target = name.to_string();
for field in &self.entity.field_instances {
if field.identifier == target {
return field
.value
.as_ref()
.map(|v| v.clone())
.unwrap_or(serde_json::Value::Null);
}
}
serde_json::Value::Null
}
}
pub struct LayerRef<'a> {
pub idx: usize,
pub indexer: Indexer,
pub layer: &'a LayerInstance,
}
impl<'a> LayerRef<'a> {
pub fn new(idx: usize, indexer: Indexer, layer: &'a LayerInstance) -> Self {
LayerRef {
layer,
indexer,
idx,
}
}
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.layer
.grid_tiles
.iter()
.chain(self.layer.auto_layer_tiles.iter())
.for_each(|tile: &TileInstance| {
let (x, y) = match tile.px.as_slice() {
&[x, y] => (px_to_grid(x), px_to_grid(y)),
_ => {
return;
}
};
cb(x, y, TileRef::new(tile));
});
}
pub fn get_z_delta(&self) -> f32 {
(self.idx as f32) / 100.0
}
pub fn get_tile_at(
&self,
x: impl AsPrimitive<isize>,
y: impl AsPrimitive<isize>,
) -> Option<TileRef> {
let idx = self.indexer.index(x, y);
match self.layer.grid_tiles.is_empty() {
true => self
.layer
.auto_layer_tiles
.get(idx)
.map(|tile| TileRef::new(tile)),
false => self
.layer
.grid_tiles
.get(idx)
.map(|tile| TileRef::new(tile)),
}
}
/// 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)
})
}
}
#[derive(Copy, Clone, Debug)]
pub struct CameraBounds {
pub left: f32,
pub top: f32,
pub bottom: f32,
pub right: f32,
}
impl CameraBounds {
pub fn get_min_x(&self, camera_width: f32) -> f32 {
self.left + (camera_width / 2.0) - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_x(&self, camera_width: f32) -> f32 {
self.right - (camera_width / 2.0) - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_min_y(&self, camera_height: f32) -> f32 {
self.bottom + (camera_height / 2.0) - (get_ldtk_tile_scale() / 2.0)
}
pub fn get_max_y(&self, camera_height: f32) -> f32 {
self.top - (camera_height / 2.0) - (get_ldtk_tile_scale() / 2.0)
}
}
impl<'w, 's> MapQuery<'w, 's> {
// --- We put our logic in static accessors because we might source a level other
// --- than the currently active one. 'active' methods are a convenience to
// --- call the static accessors on whatever the current level is
pub fn get_indexer_for(level: &Level) -> Indexer {
Indexer::new(px_to_grid(level.px_wid), px_to_grid(level.px_hei))
}
pub fn for_each_layer_of(level: &Level, mut cb: impl FnMut(LayerRef)) {
if let Some(layers) = level.layer_instances.as_ref() {
for layer in layers.iter().rev().enumerate() {
cb(LayerRef::new(
layer.0,
MapQuery::get_indexer_for(level),
layer.1,
));
}
}
}
pub fn get_entities_of(level: &Level) -> Vec<&EntityInstance> {
level
.layer_instances
.as_ref()
.map(|layers| {
layers
.iter()
.flat_map(|layer| layer.entity_instances.iter())
.collect()
})
.unwrap_or_default()
}
pub fn get_instance_refs_of(level: &Level) -> Vec<InstanceRef> {
level
.layer_instances
.as_ref()
.map(|layers| {
layers
.iter()
.flat_map(|layer| layer.entity_instances.iter())
.map(|inst| InstanceRef { entity: inst })
.collect()
})
.unwrap_or_default()
}
pub fn get_filtered_entities_of(
level: &Level,
entity_type: impl ToString,
) -> Vec<&EntityInstance> {
let e_type = entity_type.to_string();
match level.layer_instances.as_ref() {
Some(inst) => inst
.iter()
.flat_map(|layer| layer.entity_instances.iter())
.filter(|inst| inst.identifier == e_type)
.collect(),
None => Vec::new(),
}
}
pub fn get_owned_entities_of(level: &Level) -> Vec<EntityInstance> {
level
.layer_instances
.as_ref()
.map(|layers| {
layers
.iter()
.flat_map(|layer| layer.entity_instances.iter().map(|inst| inst.serde_clone()))
.collect()
})
.unwrap_or_default()
}
pub fn get_camera_bounds_of(level: &Level) -> CameraBounds {
CameraBounds {
left: 0.0,
top: level.px_hei as f32,
bottom: 0.0,
right: level.px_wid as f32,
}
}
pub fn get_active_level(&self) -> Option<&Level> {
self.active
.as_ref()
.and_then(|index| self.index.get(&index.map))
}
pub fn get_entities(&self) -> Vec<&EntityInstance> {
self.get_active_level()
.map(|level| MapQuery::get_entities_of(level))
.unwrap_or_default()
}
pub fn get_camera_bounds(&self) -> Option<CameraBounds> {
self.get_active_level().map(MapQuery::get_camera_bounds_of)
}
pub fn for_each_layer(&self, mut cb: impl FnMut(LayerRef)) {
if let Some(level) = self.get_active_level() {
Self::for_each_layer_of(level, cb);
}
}
}