diff --git a/src/layout/asset/mod.rs b/src/layout/asset/mod.rs index c22b4df..187361f 100644 --- a/src/layout/asset/mod.rs +++ b/src/layout/asset/mod.rs @@ -2,17 +2,42 @@ 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}; +use crate::{ + LoadingState, + meters, + item::lockpick::lockpick_bundle, + layout::{ + Level, + LevelAssetHandle, + asset::structs::*, + container::container_bundle, + door::door_bundle, + lock::padlock_bundle, + stairs::stairs_bundle, + tilemap::tilemap_bundle + }, + player::player_bundle, +}; pub mod structs; +pub fn check_loading( + level_handle: Res, + asset_server: Res, + mut next_state: ResMut>, +) { + if asset_server.is_loaded(level_handle.id()) { + next_state.set(LoadingState::Ready); + } +} + pub fn load_level ( - InRef(level_handle): InRef>, + level_handle: Res, mut commands: Commands, asset_server: Res, level_assets: Res>, ) { - let Some(level) = level_assets.get(level_handle) else { + let Some(level) = level_assets.get(&level_handle.0) else { error!("Could not load level asset from {level_handle:?}"); return; }; @@ -45,7 +70,7 @@ pub fn load_level ( 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() { + 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 { @@ -53,21 +78,34 @@ pub fn load_level ( } } - for StairsData {pos, floors} in level.interactive.stairs.iter() { match floors { - &0 | &1 => continue, + 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 { + if i == *floors - 1 { up = None; } + parent.spawn(stairs_bundle(&asset_server, pos, up, down)); + pos.y += meters(4.); down = Some(vec2(meters(-2.), meters(-4.))); } }, }} + + for ContainerData { pos, size, items } in level.interactive.containers.iter() { + let pos = vec2(meters(pos.x), meters(pos.y)); + + let mut container = parent.spawn(container_bundle(&asset_server, pos, *size)); + + for item in items { + // TODO: replace with proper item-by-id system + if item.id == "lockpick" { + container.with_child(lockpick_bundle(&asset_server, item.pos, item.rotated)); + } + } + } }); } diff --git a/src/layout/asset/structs/inner.rs b/src/layout/asset/structs/inner.rs index 0fe3149..7ac7dc8 100644 --- a/src/layout/asset/structs/inner.rs +++ b/src/layout/asset/structs/inner.rs @@ -171,7 +171,7 @@ impl From for ContainerData { Self { pos: pos.into(), size: size.unwrap_or_default().into(), - items, + items: items.unwrap_or_default(), } } } diff --git a/src/layout/asset/structs/mod.rs b/src/layout/asset/structs/mod.rs index 8b127bb..4cba5b0 100644 --- a/src/layout/asset/structs/mod.rs +++ b/src/layout/asset/structs/mod.rs @@ -64,7 +64,7 @@ pub struct ContainerData { #[serde(default)] pub size: UVec2, #[serde(default)] - pub items: Option>, + pub items: Vec, } #[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)] diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 680d4d4..654ee4d 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -22,8 +22,13 @@ pub struct Locked; #[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] #[reflect(Component, Debug, PartialEq, Default, Clone)] +#[require(Transform, InheritedVisibility)] pub struct Level; +#[derive(Resource, Debug, PartialEq, Eq, Default, Clone, Reflect, Deref, DerefMut)] +#[reflect(Resource, Debug, PartialEq, Default, Clone)] +pub struct LevelAssetHandle(Handle); + #[derive(EntityEvent, Reflect, Clone, Copy, PartialEq, Eq, Debug)] #[reflect(Event, Debug, PartialEq, Clone)] pub struct InteractionEvent { diff --git a/src/layout/stairs.rs b/src/layout/stairs.rs index a2c47c5..5590985 100644 --- a/src/layout/stairs.rs +++ b/src/layout/stairs.rs @@ -38,7 +38,6 @@ pub fn on_stairs_interact( return; }; let Ok(mut player_transform) = player_query.single_mut() else { - error!("Player is not single"); return; }; diff --git a/src/layout/systems.rs b/src/layout/systems.rs index f09a6b5..6caede2 100644 --- a/src/layout/systems.rs +++ b/src/layout/systems.rs @@ -6,12 +6,7 @@ use bevy::{ use bevy_rapier2d::prelude::*; use crate::{ - meters, - item::lockpick::lockpick_bundle, - player::{ - Player, - player_bundle, - }, + player::Player, }; use super::*; @@ -33,6 +28,22 @@ fn get_interactive_id( } } +fn interact_collisions_inner( + first: Entity, + second: Entity, + interactive_query: Query<(), F>, + player_query: Query<(), With>, + parent_query: Query<&ChildOf, With>, +) -> Option { + let Some(interactive_id) = get_interactive_id(first, interactive_query, parent_query) else { + return None; + }; + if player_query.get(second).is_err() { + return None; + } + Some(interactive_id) +} + pub fn detect_interact_collisions( mut commands: Commands, mut collision_events: MessageReader, @@ -44,22 +55,20 @@ pub fn detect_interact_collisions( for collision_event in collision_events.read() { match collision_event { CollisionEvent::Started(first, second, _) => { - let Some(interactive_id) = get_interactive_id(*first, interactive_query1, parent_query) else { - continue; - }; - if player_query.get(*second).is_err() { - continue; + if let Some(interactive_id) = interact_collisions_inner(*first, *second, interactive_query1, player_query, parent_query) { + commands.entity(interactive_id).insert(MayInteract); + } + if let Some(interactive_id) = interact_collisions_inner(*second, *first, interactive_query1, player_query, parent_query) { + commands.entity(interactive_id).insert(MayInteract); } - commands.entity(interactive_id).insert(MayInteract); }, CollisionEvent::Stopped(first, second, _) => { - let Some(interactive_id) = get_interactive_id(*first, interactive_query2, parent_query) else { - continue; - }; - if player_query.get(*second).is_err() { - continue; + if let Some(interactive_id) = interact_collisions_inner(*first, *second, interactive_query2, player_query, parent_query) { + commands.entity(interactive_id).remove::(); + } + if let Some(interactive_id) = interact_collisions_inner(*second, *first, interactive_query2, player_query, parent_query) { + commands.entity(interactive_id).remove::(); } - commands.entity(interactive_id).remove::(); }, } } @@ -78,66 +87,6 @@ pub fn setup_world( mut commands: Commands, asset_server: Res, ) { - 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::>(); - - commands.spawn(tilemap::tilemap_bundle(&asset_server, tiles)); - - 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(11.5), meters(1.)), true)) - .with_child(lock::padlock_bundle(&asset_server, true)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(11.5), meters(5.)), true)); - - commands.spawn(stairs::stairs_bundle( - &asset_server, - vec2(meters(8.), meters(1.)), - Some(vec2(meters(2.), meters(4.))), - None, - )); - commands.spawn(stairs::stairs_bundle( - &asset_server, - vec2(meters(8.), meters(5.)), - None, - Some(vec2(meters(-2.), meters(-4.))), - )); - - commands.spawn(container::container_bundle( - &asset_server, - vec2(meters(2.), meters(1.)), - uvec2(1, 1), - )); - commands.spawn(container::container_bundle( - &asset_server, - vec2(meters(14.), meters(1.)), - uvec2(1, 1), - )); - commands.spawn(container::container_bundle( - &asset_server, - 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)); + let level_handle = asset_server.load("levels/level.toml"); + commands.insert_resource(LevelAssetHandle(level_handle)); } diff --git a/src/lib.rs b/src/lib.rs index 9db5949..8263978 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ use bevy::{ prelude::*, ui_widgets::ScrollbarPlugin, }; +use bevy_common_assets::toml::TomlAssetPlugin; use bevy_rapier2d::{ prelude::*, rapier::prelude::IntegrationParameters, @@ -28,6 +29,14 @@ pub enum GameState { Inventory, } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States, Reflect)] +#[reflect(Clone, PartialEq, Debug, Default, Hash, State)] +pub enum LoadingState { + #[default] + Loading, + Ready, +} + fn camera_bundle() -> impl Bundle { ( Camera2d, @@ -81,9 +90,11 @@ impl Plugin for ExpeditionPlugin { RapierPhysicsPlugin::<()>::default() .with_custom_initialization(rapier_init), ScrollbarPlugin, + TomlAssetPlugin::::new(&["toml"]), input::plugin::InputAssetPlugin::::default(), input::plugin::InputAssetPlugin::::default(), )) + .init_state::() .init_state::() .insert_resource(ui::WindowSize::default()) .add_systems(Startup, ( @@ -92,12 +103,15 @@ impl Plugin for ExpeditionPlugin { )) .add_systems(Update, ( insert_entity_name, + layout::asset::check_loading + .run_if(|state: Res>| *state == LoadingState::Loading), layout::systems::detect_interact_collisions, layout::systems::lock_door, player::systems::handle_input, ui::update_window_size, ui::handle_input, )) + .add_systems(OnEnter(LoadingState::Ready), layout::asset::load_level) .add_systems(OnEnter(GameState::Inventory), ui::inventory::systems::setup_ui_inventory) .add_systems(OnExit(GameState::Inventory), ui::inventory::systems::clear_ui_inventory) .add_observer(ui::inventory::observers::on_ui_rotate) diff --git a/src/player/systems.rs b/src/player/systems.rs index e6d07ef..416debb 100644 --- a/src/player/systems.rs +++ b/src/player/systems.rs @@ -19,7 +19,9 @@ pub fn handle_input( interactables: Query>, mut player: Query<(&Player, &mut ActionState, &mut KinematicCharacterController, &mut Sprite)>, ) { - let player = player.single_mut().expect("Player should be single"); + let Ok(player) = player.single_mut() else { + return; + }; match state.get() { GameState::Running => { let (Player {speed}, mut action_state, mut controller, mut sprite) = player; diff --git a/src/tests/level.rs b/src/tests/level.rs index 1bff547..16df9e6 100644 --- a/src/tests/level.rs +++ b/src/tests/level.rs @@ -1,11 +1,14 @@ -use super::super::*; +use bevy::prelude::*; +use bevy_common_assets::toml::TomlAssetPlugin; + +use crate::layout::asset::structs::LevelAsset; #[test] -fn deserialize_levels() { +fn deserialize_from_str() { 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(); + 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); @@ -14,3 +17,20 @@ fn deserialize_levels() { assert_eq!(&tiles, other_tiles); } } + +#[test] +fn deserialize_from_app_asset() { + let mut app = App::new(); + app.add_plugins(( + MinimalPlugins, + AssetPlugin { + file_path: "../../assets".to_string(), + ..default() + }, + TomlAssetPlugin::::new(&["toml"]), + )); + + if let Err(error) = app.world_mut().run_system_cached(|asset_server: Res| { + let _ = asset_server.load::("levels/level.toml"); + }) { panic!("{error}") }; +}