From 24153e476fbcf4a3f248d26a43357651ef5a683b Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Fri, 27 Mar 2026 13:04:47 +0300 Subject: [PATCH 1/2] feat: Updated tilemap bundle - Layout is now defined from bottom left corner - Added rotation to lockpick bundle - Added level TOML structures that match current level --- assets/levels/level.toml | 110 +++++++++++++++++++++++++++++++++++ assets/levels/level_alt.toml | 48 +++++++++++++++ src/item/lockpick.rs | 8 ++- src/layout/asset.rs | 1 + src/layout/mod.rs | 1 + src/layout/systems.rs | 86 ++++++++++++--------------- src/layout/tilemap.rs | 45 +++++++------- 7 files changed, 226 insertions(+), 73 deletions(-) create mode 100644 assets/levels/level.toml create mode 100644 assets/levels/level_alt.toml create mode 100644 src/layout/asset.rs diff --git a/assets/levels/level.toml b/assets/levels/level.toml new file mode 100644 index 0000000..39eaf4a --- /dev/null +++ b/assets/levels/level.toml @@ -0,0 +1,110 @@ +[[tiles.floors]] +x = 0 +y = 0 +w = 16 + +[[tiles.floors]] +x = 0 +y = 4 +w = 16 + +[[tiles.floors]] +x = 0 +y = 8 +w = 16 + +[[tiles.walls]] +x = 0 +y = 1 +h = 3 + +[[tiles.walls]] +x = 0 +y = 5 +h = 3 + +[[tiles.walls]] +x = 15 +y = 1 +h = 3 + +[[tiles.walls]] +x = 15 +y = 5 +h = 3 + +[[tiles.wall_connectors]] +x = 4 +y = 3 + +[[tiles.wall_connectors]] +x = 4 +y = 7 + +[[tiles.wall_connectors]] +x = 11 +y = 3 + +[[tiles.wall_connectors]] +x = 11 +y = 7 + +[interactive.player] +x = 1 +y = 1 + +[[interactive.doors]] +x = 4 +y = 1 +facing = "right" + +[[interactive.doors]] +x = 4 +y = 5 +facing = "right" +lock = "right" + +[[interactive.doors]] +x = 11 +y = 1 +facing = "left" +lock = "left" + +[[interactive.doors]] +x = 11 +y = 1 +facing = "left" + +[[interactive.stairs]] +x = 8 +y = 1 +floors = 2 + +[[interactive.containers]] +x = 2 +y = 1 + +[[interactive.containers]] +x = 14 +y = 1 + +[[interactive.containers]] +x = 2 +y = 5 +w = 4 +h = 4 + +[interactive.containers.items] +id = "lockpick" +x = 2 +y = 2 + +[[interactive.containers]] +x = 14 +y = 5 +w = 2 +h = 2 + +[interactive.containers.items] +id = "lockpick" +rotated = true diff --git a/assets/levels/level_alt.toml b/assets/levels/level_alt.toml new file mode 100644 index 0000000..04d4343 --- /dev/null +++ b/assets/levels/level_alt.toml @@ -0,0 +1,48 @@ +[tiles] +floors = [ + { x = 0, y = 0, w = 16 }, + { x = 0, y = 4, w = 16 }, + { x = 0, y = 8, w = 16 }, +] +walls = [ + { x = 0, y = 1, h = 3 }, + { x = 0, y = 5, h = 3 }, + { x = 15, y = 1, h = 3 }, + { x = 15, y = 5, h = 3 }, +] +wall_connectors = [ + { x = 4, y = 3 }, + { x = 4, y = 7 }, + { x = 11, y = 3 }, + { x = 11, y = 7 }, +] + +[interactive] +player = { x = 1, y = 1 } +doors = [ + { x = 4, y = 1, facing = "right" }, + { x = 4, y = 5, facing = "right", lock = "right" }, + { x = 11, y = 1, facing = "left", lock = "left" }, + { x = 11, y = 5, facing = "left" }, +] +stairs = [ + { x = 8, y = 1, floors = 2 }, +] +containers = [ + { x = 2, y = 1 }, + { x = 14, y = 1 }, + { + x = 2, y = 5, + w = 4, h = 4, + items = [ + { id = "lockpick", x = 2, y = 2 }, + ], + }, + { + x = 14, y = 5, + w = 2, h = 2, + items = [ + { id = "lockpick", rotated = true }, + ], + }, +] diff --git a/src/item/lockpick.rs b/src/item/lockpick.rs index 32cd95a..8cd8dea 100644 --- a/src/item/lockpick.rs +++ b/src/item/lockpick.rs @@ -9,10 +9,14 @@ const LOCKPICK_SPRITE: &'static str = "sprites/items/lockpick.png"; #[require(Item)] pub struct Lockpick; -pub fn lockpick_bundle(asset_server: &Res, position: UVec2) -> impl Bundle { +pub fn lockpick_bundle(asset_server: &Res, position: UVec2, rotated: bool) -> impl Bundle { let image = asset_server.load(LOCKPICK_SPRITE); + let mut item = Item::new(uvec2(1, 1), position); + if rotated { + item.rotate(); + } ( - Item::new(uvec2(1, 1), position), + item, ItemData { image, name: "Lockpick".into(), diff --git a/src/layout/asset.rs b/src/layout/asset.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/layout/asset.rs @@ -0,0 +1 @@ + diff --git a/src/layout/mod.rs b/src/layout/mod.rs index d298e8e..32f6e72 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,5 +1,6 @@ use bevy::prelude::*; +pub mod asset; pub mod container; pub mod door; pub mod lock; diff --git a/src/layout/systems.rs b/src/layout/systems.rs index d15af28..f09a6b5 100644 --- a/src/layout/systems.rs +++ b/src/layout/systems.rs @@ -78,80 +78,66 @@ pub fn setup_world( mut commands: Commands, asset_server: Res, ) { - // floor 1F - let mut tiles = (0..16).map(|x| { - (0, uvec2(x, 1)) - }).collect::>(); - - // floor/ceil 1-2F - tiles.extend((0..16).map(|x| { - (0, uvec2(x, 5)) - })); - - // ceil 2F - tiles.extend((0..16).map(|x| { - (0, uvec2(x, 9)) - })); + let tiles = [ + ( 0, ( 0, 0, 16, 1 )), // 1F floor + ( 0, ( 0, 4, 16, 1 )), // 1F ceiling / 2F floor + ( 0, ( 0, 8, 16, 1 )), // 2F ceiling + + ( 1, ( 0, 1, 1, 3 )), // 1F left wall + ( 1, ( 0, 5, 1, 3 )), // 2F left wall + ( 1, ( 15, 1, 1, 3 )), // 1F right wall + ( 1, ( 15, 5, 1, 3 )), // 2F right wall + + ( 2, ( 4, 3, 1, 1 )), // 1F left door connector + ( 2, ( 4, 7, 1, 1 )), // 2F left door connector + ( 2, ( 11, 3, 1, 1 )), // 1F right door connector + ( 2, ( 11, 7, 1, 1 )), // 2F right door connector + ].into_iter().map(|(id, (x, y, w, h))| { + (id, URect::from_corners(uvec2(x, y), uvec2(x + w - 1, y + h - 1))) + }).collect::>(); - // walls - tiles.extend([ - (0, 4), (0, 3), (0, 2), - (0, 8), (0, 7), (0, 6), - (15, 4), (15, 3), (15, 2), - (15, 8), (15, 7), (15, 6), - ].iter().map(|(x, y)| { - (1, uvec2(*x, *y)) - })); - - // wall connectors - tiles.extend([ - (4, 8), (4, 4), (11, 8), (11, 4), - ].iter().map(|(x, y)| { - (2, uvec2(*x, *y)) - })); - - commands.spawn(tilemap::tilemap_bundle(&asset_server, uvec2(16, 10), tiles)); + commands.spawn(tilemap::tilemap_bundle(&asset_server, tiles)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(-3.5), 0.), false)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(-3.5), meters(4.)), false)) + commands.spawn(player_bundle(&asset_server, vec2(meters(1.5), meters(1.)))); + + commands.spawn(door::door_bundle(&asset_server, vec2(meters(4.5), meters(1.)), false)); + commands.spawn(door::door_bundle(&asset_server, vec2(meters(4.5), meters(5.)), false)) .with_child(lock::padlock_bundle(&asset_server, false)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(3.5), 0.), true)) + commands.spawn(door::door_bundle(&asset_server, vec2(meters(11.5), meters(1.)), true)) .with_child(lock::padlock_bundle(&asset_server, true)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(3.5), meters(4.)), true)); + commands.spawn(door::door_bundle(&asset_server, vec2(meters(11.5), meters(5.)), true)); commands.spawn(stairs::stairs_bundle( &asset_server, - vec2(meters(0.), 0.), + vec2(meters(8.), meters(1.)), Some(vec2(meters(2.), meters(4.))), None, )); commands.spawn(stairs::stairs_bundle( &asset_server, - vec2(meters(0.), meters(4.)), + vec2(meters(8.), meters(5.)), None, Some(vec2(meters(-2.), meters(-4.))), )); commands.spawn(container::container_bundle( &asset_server, - vec2(meters(-6.), 0.), + vec2(meters(2.), meters(1.)), uvec2(1, 1), )); commands.spawn(container::container_bundle( &asset_server, - vec2(meters(-6.), meters(4.)), - uvec2(1, 1), - )).with_child(lockpick_bundle(&asset_server, UVec2::ZERO)); - commands.spawn(container::container_bundle( - &asset_server, - vec2(meters(6.), 0.), + vec2(meters(14.), meters(1.)), uvec2(1, 1), )); commands.spawn(container::container_bundle( &asset_server, - vec2(meters(6.), meters(4.)), - uvec2(1, 1), - )).with_child(lockpick_bundle(&asset_server, UVec2::ZERO)); - - commands.spawn(player_bundle(&asset_server, vec2(meters(-6.), meters(4.)))); + vec2(meters(2.), meters(5.)), + uvec2(4, 4), + )).with_child(lockpick_bundle(&asset_server, uvec2(2, 2), false)); + commands.spawn(container::container_bundle( + &asset_server, + vec2(meters(14.), meters(5.)), + uvec2(2, 2), + )).with_child(lockpick_bundle(&asset_server, UVec2::ZERO, true)); } diff --git a/src/layout/tilemap.rs b/src/layout/tilemap.rs index 7a9bd7d..1e81175 100644 --- a/src/layout/tilemap.rs +++ b/src/layout/tilemap.rs @@ -23,11 +23,15 @@ pub struct Tilemap; pub fn tilemap_bundle( asset_server: &Res, - size: UVec2, - tiles: Vec<(u16, UVec2)>, + tiles: Vec<(u16, URect)>, ) -> impl Bundle { + let mut size = uvec2(0, 0); + for tile in tiles.iter() { + size.x = size.x.max(tile.1.max.x + 1); + size.y = size.y.max(tile.1.max.y + 1); + } let tile_data: Vec> = (0..size.element_product()).map(|xy| { - if let Some((id, _)) = tiles.iter().find(|(_, pos)| pos.x == xy % size.x && pos.y == xy / size.x) { + if let Some((id, _)) = tiles.iter().find(|(_, rect)| rect.contains(uvec2(xy % size.x, xy / size.x))) { Some(TileData::from_tileset_index(*id)) } else { None } }).collect(); @@ -44,27 +48,26 @@ pub fn tilemap_bundle( ), ..default() }, - Transform::from_xyz(0., meters(0.5) * size.y as f32 - meters(3.), 0.), + Transform::from_xyz( + meters(0.5) * size.x as f32, + meters(0.5) * size.y as f32 - meters(1.), + 0., + ), TilemapChunkTileData(tile_data), - Children::spawn(SpawnIter(tiles.into_iter().map(move |(_, pos)| {( - // TODO: optimize colliders - Collider::cuboid(meters(0.5), meters(0.5)), + Children::spawn(SpawnIter(tiles.into_iter().map(move |(_, rect)| { + let mut rect = rect.as_rect(); + rect.max = vec2(rect.max.x + 1., rect.max.y + 1.); + + let (width, height) = (rect.width(), rect.height()); + let offset = rect.center(); + ( + Collider::cuboid(meters(width * 0.5), meters(height * 0.5)), Transform::from_xyz( - meters(0.5 + pos.x as f32 - (size.x as f32 * 0.5)), - meters(0.5 + pos.y as f32 - (size.y as f32 * 0.5)), + meters(offset.x - size.x as f32 * 0.5), + meters(offset.y - size.y as f32 * 0.5), 0., ), - )}) - )), - //Children::spawn(SpawnIter(colliders.into_iter() - // .map(move |(collider, pos)| {( - // collider.clone(), - // Transform::from_xyz( - // pos.x - meters(0.5) * size.x as f32, - // pos.y - meters(0.5) * size.y as f32, - // 0. - // ), - // )}) - //)), + )} + ))), ) } From 0ab26207248c85c1a01b6711ad0957e0dab417ca Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Fri, 27 Mar 2026 18:31:46 +0300 Subject: [PATCH 2/2] feat: Unfinished level loading - Added level struct - Added loading tests - Partially implemented level setting up system --- assets/levels/level.toml | 9 +- assets/levels/level_alt.toml | 5 +- src/layout/asset.rs | 1 - src/layout/asset/mod.rs | 73 ++++++++++ src/layout/asset/structs/inner.rs | 212 ++++++++++++++++++++++++++++++ src/layout/asset/structs/mod.rs | 98 ++++++++++++++ src/layout/mod.rs | 4 + src/tests/level.rs | 16 +++ src/tests/mod.rs | 1 + 9 files changed, 414 insertions(+), 5 deletions(-) delete mode 100644 src/layout/asset.rs create mode 100644 src/layout/asset/mod.rs create mode 100644 src/layout/asset/structs/inner.rs create mode 100644 src/layout/asset/structs/mod.rs create mode 100644 src/tests/level.rs diff --git a/assets/levels/level.toml b/assets/levels/level.toml index 39eaf4a..b432e2b 100644 --- a/assets/levels/level.toml +++ b/assets/levels/level.toml @@ -1,3 +1,6 @@ +[meta] +tiles = [ "floors", "walls", "wall_connectors" ] + [[tiles.floors]] x = 0 y = 0 @@ -72,7 +75,7 @@ lock = "left" [[interactive.doors]] x = 11 -y = 1 +y = 5 facing = "left" [[interactive.stairs]] @@ -94,7 +97,7 @@ y = 5 w = 4 h = 4 -[interactive.containers.items] +[[interactive.containers.items]] id = "lockpick" x = 2 y = 2 @@ -105,6 +108,6 @@ y = 5 w = 2 h = 2 -[interactive.containers.items] +[[interactive.containers.items]] id = "lockpick" rotated = true diff --git a/assets/levels/level_alt.toml b/assets/levels/level_alt.toml index 04d4343..b1aa68a 100644 --- a/assets/levels/level_alt.toml +++ b/assets/levels/level_alt.toml @@ -1,3 +1,6 @@ +[meta] +tiles = [ "floors", "walls", "wall_connectors" ] + [tiles] floors = [ { x = 0, y = 0, w = 16 }, @@ -26,7 +29,7 @@ doors = [ { x = 11, y = 5, facing = "left" }, ] stairs = [ - { x = 8, y = 1, floors = 2 }, + { x = 8, y = 1 }, ] containers = [ { x = 2, y = 1 }, diff --git a/src/layout/asset.rs b/src/layout/asset.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/layout/asset.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/layout/asset/mod.rs b/src/layout/asset/mod.rs new file mode 100644 index 0000000..c22b4df --- /dev/null +++ b/src/layout/asset/mod.rs @@ -0,0 +1,73 @@ +use std::collections::HashMap; + +use bevy::prelude::*; + +use crate::{layout::{Level, asset::structs::{DoorData, StairsData}, door::door_bundle, lock::padlock_bundle, stairs::stairs_bundle, tilemap::tilemap_bundle}, meters, player::player_bundle}; + +pub mod structs; + +pub fn load_level ( + InRef(level_handle): InRef>, + mut commands: Commands, + asset_server: Res, + level_assets: Res>, +) { + let Some(level) = level_assets.get(level_handle) else { + error!("Could not load level asset from {level_handle:?}"); + return; + }; + + let Some(default_tile) = level.meta.tiles.first() else { + error!("Level meta does not contain tile ids"); + return; + }; + + let tile_ids: HashMap = level.meta.tiles.iter() + .enumerate() + .map(|(v, k)| (k.to_owned(), v as u16)) + .collect(); + + let tiles: Vec<(u16, URect)> = level.tiles.iter() + .flat_map(|(id, tiles)| { + let id = match tile_ids.get(id.as_str()) { + Some(id) => *id, + None => { + warn!("Tile ID {id} not found in level meta, using {default_tile}..."); + 0 + } + }; + tiles.clone().into_iter().map(move |t| (id, t)) + }).collect(); + + let player_pos = vec2(meters(level.interactive.player.x + 0.5), meters(level.interactive.player.y)); + + commands.spawn(Level).with_children(|parent| { + parent.spawn(tilemap_bundle(&asset_server, tiles)); + parent.spawn(player_bundle(&asset_server, player_pos)); + + for DoorData {pos, facing_left, lock} in level.interactive.doors.iter() { + let door_pos = vec2(meters(pos.x + 0.5), meters(pos.y)); + let mut door = parent.spawn(door_bundle(&asset_server, door_pos, *facing_left)); + if let Some(lock_facing_left) = lock { + door.with_child(padlock_bundle(&asset_server, *lock_facing_left)); + } + } + + for StairsData {pos, floors} in level.interactive.stairs.iter() { match floors { + &0 | &1 => continue, + _ => { + let mut pos = vec2(meters(pos.x), meters(pos.y)); + let mut down = None; + let mut up = Some(vec2(meters(2.), meters(4.))); + for i in 0..*floors { + parent.spawn(stairs_bundle(&asset_server, pos, up, down)); + pos.y += meters(4.); + if i == floors - 1 { + up = None; + } + down = Some(vec2(meters(-2.), meters(-4.))); + } + }, + }} + }); +} diff --git a/src/layout/asset/structs/inner.rs b/src/layout/asset/structs/inner.rs new file mode 100644 index 0000000..0fe3149 --- /dev/null +++ b/src/layout/asset/structs/inner.rs @@ -0,0 +1,212 @@ +use super::*; + +pub(super) fn default_floors() -> u8 { 2 } + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(default)] +pub(super) struct Pos { + pub x: f32, + pub y: f32, +} + +impl From for Vec2 { + fn from(Pos { x, y }: Pos) -> Self { + Self { x, y } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(default)] +pub(super) struct UPos { + pub x: u32, + pub y: u32, +} + +impl From for UVec2 { + fn from(UPos { x, y }: UPos) -> Self { + Self { x, y } + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(default)] +pub(super) struct USize { + pub w: u32, + pub h: u32, +} + +impl Default for USize { + fn default() -> Self { + Self { w: 1, h: 1 } + } +} + +impl From for UVec2 { + fn from(USize { w, h }: USize) -> Self { + Self { x: w, y: h } + } +} + +#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(default)] +pub(super) struct USizeRect { + #[serde(flatten)] + pub pos: UPos, + #[serde(flatten)] + pub size: USize, +} + +impl From for URect { + fn from(USizeRect { pos, size: USize { w, h } }: USizeRect) -> Self { + URect::from_corners(pos.into(), uvec2(pos.x + w - 1, pos.y + h - 1)) + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub(super) enum Facing { + #[default] + Left, + Right, +} + +impl From for bool { + fn from(value: Facing) -> Self { + match value { + Facing::Left => true, + Facing::Right => false, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +pub(super) struct DoorDataInner { + #[serde(flatten)] + pub pos: Pos, + #[serde(default)] + pub facing: Option, + #[serde(default)] + pub lock: Option, +} + +impl From for DoorData { + fn from(DoorDataInner { pos, facing, lock }: DoorDataInner) -> Self { + let lock = + if let Some(lock) = lock { Some(lock.into()) } + else { None }; + Self { + pos: pos.into(), + facing_left: facing.unwrap_or_default().into(), + lock, + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +pub(super) struct StairsInnerData { + #[serde(flatten)] + pub pos: Pos, + #[serde(default = "default_floors")] + pub floors: u8, +} + +impl From for StairsData { + fn from(StairsInnerData { pos, floors }: StairsInnerData) -> Self { + Self { + pos: pos.into(), + floors, + } + } +} + +impl Default for StairsInnerData { + fn default() -> Self { + Self { + pos: Pos::default(), + floors: default_floors(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +pub(super) struct ItemDataInner { + pub id: String, + #[serde(default, flatten)] + pub pos: UPos, + #[serde(default)] + pub rotated: bool, +} + +impl From for ItemData { + fn from(ItemDataInner { id, pos, rotated }: ItemDataInner) -> Self { + Self { + id, + pos: pos.into(), + rotated, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +pub(super) struct ContainerDataInner { + #[serde(flatten)] + pub pos: Pos, + #[serde(flatten, default)] + pub size: Option, + #[serde(default)] + pub items: Option>, +} + +impl From for ContainerData { + fn from(ContainerDataInner { pos, size, items }: ContainerDataInner) -> Self { + Self { + pos: pos.into(), + size: size.unwrap_or_default().into(), + items, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +pub(super) struct InteractiveInner { + pub player: Pos, + #[serde(default)] + pub doors: Option>, + #[serde(default)] + pub stairs: Option>, + #[serde(default)] + pub containers: Option>, +} + +impl From for Interactive { + fn from(InteractiveInner { player, doors, stairs, containers }: InteractiveInner) -> Self { + Self { + player: player.into(), + doors: doors.unwrap_or_default(), + stairs: stairs.unwrap_or_default(), + containers: containers.unwrap_or_default(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(transparent)] +pub(super) struct TilesInner(HashMap>); + +impl From for Tiles { + fn from(TilesInner(tiles): TilesInner) -> Self { + // This is probably the funniest one-liner I've ever written + Tiles { tiles: tiles.into_iter().map(|(k, v)| (k, v.into_iter().map(|v| v.into()).collect())).collect() } + } +} diff --git a/src/layout/asset/structs/mod.rs b/src/layout/asset/structs/mod.rs new file mode 100644 index 0000000..8b127bb --- /dev/null +++ b/src/layout/asset/structs/mod.rs @@ -0,0 +1,98 @@ +use std::collections::HashMap; + +use bevy::prelude::*; + +use serde::{ + Deserialize, + Serialize, +}; + +mod inner; + +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Reflect)] +#[reflect(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct Meta { + pub tiles: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(from = "inner::DoorDataInner")] +pub struct DoorData { + pub pos: Vec2, + #[serde(default)] + pub facing_left: bool, + #[serde(default)] + pub lock: Option, +} + + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(from = "inner::StairsInnerData")] +pub struct StairsData { + pub pos: Vec2, + #[serde(default = "default_floors")] + pub floors: u8, +} + +impl Default for StairsData { + fn default() -> Self { + Self { + pos: Vec2::default(), + floors: inner::default_floors(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(from = "inner::ItemDataInner")] +pub struct ItemData { + pub id: String, + #[serde(default)] + pub pos: UVec2, + #[serde(default)] + pub rotated: bool, +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(from = "inner::ContainerDataInner")] +pub struct ContainerData { + pub pos: Vec2, + #[serde(default)] + pub size: UVec2, + #[serde(default)] + pub items: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(from = "inner::InteractiveInner")] +pub struct Interactive { + pub player: Vec2, + #[serde(default)] + pub doors: Vec, + #[serde(default)] + pub stairs: Vec, + #[serde(default)] + pub containers: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Deref, DerefMut, Reflect)] +#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)] +#[serde(from = "inner::TilesInner")] +pub struct Tiles { + #[serde(flatten)] + pub tiles: HashMap>, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Reflect, Asset)] +#[reflect(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct LevelAsset { + pub meta: Meta, + pub tiles: Tiles, + #[serde(default)] + pub interactive: Interactive, +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 32f6e72..680d4d4 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -20,6 +20,10 @@ pub struct InteractiveObject; #[reflect(Component, Debug, PartialEq, Default, Clone)] pub struct Locked; +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] +pub struct Level; + #[derive(EntityEvent, Reflect, Clone, Copy, PartialEq, Eq, Debug)] #[reflect(Event, Debug, PartialEq, Clone)] pub struct InteractionEvent { diff --git a/src/tests/level.rs b/src/tests/level.rs new file mode 100644 index 0000000..1bff547 --- /dev/null +++ b/src/tests/level.rs @@ -0,0 +1,16 @@ +use super::super::*; + +#[test] +fn deserialize_levels() { + let level_str = include_str!("../../assets/levels/level.toml"); + let level_alt_str = include_str!("../../assets/levels/level_alt.toml"); + let level = toml::de::from_str::(level_str).unwrap(); + let level_alt = toml::de::from_str::(level_alt_str).unwrap(); + + assert_eq!(level.meta, level_alt.meta); + assert_eq!(level.interactive, level_alt.interactive); + for (tiles_id, tiles) in level.tiles.tiles { + let (_, other_tiles) = level_alt.tiles.iter().find(|(k, _)| k == &&tiles_id).unwrap(); + assert_eq!(&tiles, other_tiles); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 416574d..19f64cf 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,2 +1,3 @@ mod input; mod inventory; +mod level;