diff --git a/src/inventory/item/lockpick.rs b/src/inventory/item/lockpick.rs new file mode 100644 index 0000000..dfde1c5 --- /dev/null +++ b/src/inventory/item/lockpick.rs @@ -0,0 +1,20 @@ +use bevy::prelude::*; + +use super::*; + +// TODO: replace with proper sprite +const LOCKPICK_SPRITE: &'static str = "sprites/items/choco_bar.png"; + +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] +#[require(Item)] +pub struct Lockpick; + +pub fn lockpick_bundle(asset_server: &Res, position: UVec2) -> impl Bundle { + let image = asset_server.load(LOCKPICK_SPRITE); + ( + Item::new_positioned(uvec2(1, 1), position), + ItemImage(image), + Lockpick, + ) +} diff --git a/src/inventory/item.rs b/src/inventory/item/mod.rs similarity index 74% rename from src/inventory/item.rs rename to src/inventory/item/mod.rs index d61a1d4..d7ec437 100644 --- a/src/inventory/item.rs +++ b/src/inventory/item/mod.rs @@ -2,14 +2,32 @@ use std::mem::swap; use bevy::prelude::*; +pub mod lockpick; + +#[derive(Component, Debug, Deref, DerefMut, PartialEq, Eq, Default, Clone, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] +pub struct ItemImage(Handle); + +// TODO: get rid of Option in position, it's no longer needed #[derive(Component, Clone, Debug, Reflect, PartialEq, Eq)] #[reflect(Component, Clone, Debug, PartialEq)] +#[require(ItemImage)] pub struct Item { pub size: UVec2, pub position: Option, pub rotated: bool, } +impl Default for Item { + fn default() -> Self { + Self { + size: uvec2(1, 1), + position: Some(uvec2(0, 0)), + rotated: false, + } + } +} + impl Item { pub fn new(size: UVec2) -> Self { Self { size, position: None, rotated: false } diff --git a/src/layout/container.rs b/src/layout/container.rs index ac41a5b..62f5f0c 100644 --- a/src/layout/container.rs +++ b/src/layout/container.rs @@ -7,7 +7,6 @@ use crate::{ inventory::{ ActiveInventory, Inventory, - item::Item, } }; @@ -42,7 +41,6 @@ pub fn container_bundle( asset_server: &Res, position: Vec2, inventory_size: UVec2, - items: Vec, ) -> impl Bundle { let image = asset_server.load(CRATE_CLOSED_ASSET); ( @@ -51,7 +49,6 @@ pub fn container_bundle( Sprite::from_image(image), Inventory::new(inventory_size), Children::spawn(( - SpawnIter(items.into_iter()), Spawn(( Collider::cuboid(meters(1.), meters(1.)), Sensor, diff --git a/src/layout/lock.rs b/src/layout/lock.rs new file mode 100644 index 0000000..4f0a6f8 --- /dev/null +++ b/src/layout/lock.rs @@ -0,0 +1,66 @@ +use bevy::prelude::*; +use bevy_rapier2d::prelude::*; + +use crate::{inventory::{Inventory, item::lockpick::Lockpick}, meters, player::Player}; + +use super::*; + +const PADLOCK_IMAGE_PATH: &'static str = "sprites/interactive/padlock.png"; + +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] +#[require(InteractiveObject)] +pub struct Padlock; + +pub fn on_padlock_interaction( + event: On, + mut commands: Commands, + query: Query<(&ChildOf, Entity), With>, + inventory_query: Query<(&Children, &ChildOf), With>, + player_query: Query<(), With>, + lockpick_query: Query<(), With>, +) { + let Ok((parent, lock_id)) = query.get(event.entity) else { + return; + }; + + // find lockpick in player inventory + let mut lockpick_id = None; + for (items, inventory_parent) in inventory_query { + if player_query.get(inventory_parent.0).is_err() { + continue; + } + for item_id in items { + if lockpick_query.get(*item_id).is_ok() { + lockpick_id = Some(*item_id); + break; + } + } + }; + let Some(lockpick_id) = lockpick_id else { + return; + }; + + commands.entity(parent.0).remove::(); + commands.entity(lock_id).despawn(); + commands.entity(lockpick_id).despawn(); +} + +pub fn padlock_bundle(asset_server: &Res, facing_left: bool) -> impl Bundle { + let image = asset_server.load(PADLOCK_IMAGE_PATH); + let sign = if facing_left { -1. } else { 1. }; + ( + Padlock, + Sprite { + image, + flip_x: facing_left, + ..default() + }, + Transform::from_xyz(meters(sign * 0.125), meters(0.), 0.), + Children::spawn_one(( + Transform::from_xyz(meters(sign * 0.1875), 0., 0.), + Collider::cuboid(meters(0.1875), meters(1.)), + Sensor, + )), + ) +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 36ae455..d298e8e 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -2,6 +2,7 @@ use bevy::prelude::*; pub mod container; pub mod door; +pub mod lock; pub mod stairs; pub mod systems; pub mod tilemap; diff --git a/src/layout/systems.rs b/src/layout/systems.rs index 0c36a82..288083e 100644 --- a/src/layout/systems.rs +++ b/src/layout/systems.rs @@ -2,12 +2,16 @@ use bevy::{ ecs::query::QueryFilter, prelude::*, }; + use bevy_rapier2d::prelude::*; use crate::{ meters, - inventory::item::Item, - player::Player, + inventory::item::lockpick::lockpick_bundle, + player::{ + Player, + player_bundle, + }, }; use super::*; @@ -61,20 +65,19 @@ pub fn detect_interact_collisions( } } +pub fn lock_door( + mut commands: Commands, + query: Query<&ChildOf, Added>, +) { + for parent in query { + commands.entity(parent.0).insert(Locked); + } +} + pub fn setup_world( mut commands: Commands, asset_server: Res, ) { - let items = vec![ - Item::new_positioned(uvec2(1, 1), uvec2(0, 0)), - Item::new_positioned(uvec2(2, 1), uvec2(1, 0)), - Item::new_positioned(uvec2(3, 1), uvec2(3, 0)), - Item::new_positioned(uvec2(2, 2), uvec2(6, 0)), - Item::new_positioned(uvec2(6, 2), uvec2(0, 1)), - Item::new_positioned(uvec2(2, 3), uvec2(6, 2)), - Item::new_positioned(uvec2(4, 4), uvec2(0, 4)), - ]; - // floor 1F let mut tiles = (0..16).map(|x| { (0, uvec2(x, 1)) @@ -102,46 +105,53 @@ pub fn setup_world( // wall connectors tiles.extend([ - (13, 4), (11, 4), (9, 4), + (4, 8), (4, 4), (11, 8), (11, 4), ].iter().map(|(x, y)| { (2, uvec2(*x, *y)) })); - - let colliders: Vec<(Collider, Vec2)> = vec![ - // 1F - (Collider::cuboid(meters(0.5), meters(1.5)), - vec2(meters(0.5), meters(-4.5))), - (Collider::cuboid(meters(0.5), meters(1.5)), - vec2(meters(15.5), meters(-4.5))), - // 2F - (Collider::cuboid(meters(0.5), meters(1.5)), - vec2(meters(0.5), meters(-0.5))), - (Collider::cuboid(meters(0.5), meters(1.5)), - vec2(meters(15.5), meters(-0.5))), - ]; - commands.spawn(door::door_bundle(&asset_server, vec2(meters(1.5), 0.), true)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(3.5), 0.), false)); - commands.spawn(door::door_bundle(&asset_server, vec2(meters(5.5), 0.), false)).insert(Locked); - commands.spawn(container::container_bundle(&asset_server, vec2(meters(-2.), 0.), uvec2(8, 8), items.clone())); - commands.spawn(container::container_bundle(&asset_server, vec2(meters(2.), meters(4.)), uvec2(10, 8), items.clone())); - commands.spawn(tilemap::tilemap_bundle(&asset_server, uvec2(16, 16), tiles, colliders)); + commands.spawn(tilemap::tilemap_bundle(&asset_server, uvec2(16, 10), 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)) + .with_child(lock::padlock_bundle(&asset_server, false)); + commands.spawn(door::door_bundle(&asset_server, vec2(meters(3.5), 0.), 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(stairs::stairs_bundle( &asset_server, - vec2(meters(-5.), 0.), + vec2(meters(0.), 0.), Some(vec2(meters(2.), meters(4.))), None, )); commands.spawn(stairs::stairs_bundle( &asset_server, - vec2(meters(-5.), meters(4.)), - Some(vec2(meters(2.), meters(4.))), - Some(vec2(meters(-2.), meters(-4.))), - )); - commands.spawn(stairs::stairs_bundle( - &asset_server, - vec2(meters(-5.), meters(8.)), + vec2(meters(0.), meters(4.)), None, Some(vec2(meters(-2.), meters(-4.))), )); + + commands.spawn(container::container_bundle( + &asset_server, + vec2(meters(-6.), 0.), + 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.), + 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.), 0.))); } diff --git a/src/layout/tilemap.rs b/src/layout/tilemap.rs index 1e9e895..7a9bd7d 100644 --- a/src/layout/tilemap.rs +++ b/src/layout/tilemap.rs @@ -25,7 +25,6 @@ pub fn tilemap_bundle( asset_server: &Res, size: UVec2, tiles: Vec<(u16, UVec2)>, - colliders: Vec<(Collider, Vec2)>, ) -> impl Bundle { 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) { @@ -47,15 +46,25 @@ pub fn tilemap_bundle( }, Transform::from_xyz(0., meters(0.5) * size.y as f32 - meters(3.), 0.), TilemapChunkTileData(tile_data), - Children::spawn(SpawnIter(colliders.into_iter() - .map(move |(collider, pos)| {( - collider.clone(), + Children::spawn(SpawnIter(tiles.into_iter().map(move |(_, pos)| {( + // TODO: optimize colliders + Collider::cuboid(meters(0.5), meters(0.5)), Transform::from_xyz( - pos.x - meters(0.5) * size.x as f32, - pos.y, - 0. + 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)), + 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. + // ), + // )}) + //)), ) } diff --git a/src/lib.rs b/src/lib.rs index 5c8bff3..28fc673 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,11 +88,11 @@ impl Plugin for ExpeditionPlugin { .add_systems(Startup, ( setup_global, layout::systems::setup_world, - player::systems::setup_player, )) .add_systems(Update, ( insert_entity_name, layout::systems::detect_interact_collisions, + layout::systems::lock_door, player::systems::handle_input, ui::update_window_size, ui::handle_input, @@ -102,6 +102,7 @@ impl Plugin for ExpeditionPlugin { .add_observer(ui::inventory::observers::on_ui_rotate) .add_observer(layout::container::on_container_interact) .add_observer(layout::door::on_door_interact) + .add_observer(layout::lock::on_padlock_interaction) .add_observer(layout::stairs::on_stairs_interact); } } diff --git a/src/player/mod.rs b/src/player/mod.rs index 8e99702..911963a 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -22,18 +22,18 @@ impl Default for Player { } } -pub fn player_bundle(asset_server: &Res) -> impl Bundle { +pub fn player_bundle(asset_server: &Res, position: Vec2) -> impl Bundle { let image = asset_server.load("sprites/player/player.png"); ( Player::default(), Sprite::from_image(image), - Transform::from_xyz(0f32, 0f32, 1f32), + Transform::from_xyz(position.x, position.y, 1f32), Action::default_input_map(), Name::new("Player"), RigidBody::KinematicPositionBased, KinematicCharacterController::default(), ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC, - Collider::cuboid(meters(0.5), meters(1.)), + Collider::cuboid(meters(0.3), meters(0.9)), ActiveEvents::COLLISION_EVENTS, Sleeping::disabled(), children![ diff --git a/src/player/systems.rs b/src/player/systems.rs index 7303d96..e6d07ef 100644 --- a/src/player/systems.rs +++ b/src/player/systems.rs @@ -11,10 +11,6 @@ use crate::{ use super::*; -pub fn setup_player(mut commands: Commands, asset_server: Res) { - commands.spawn(player_bundle(&asset_server)); -} - pub fn handle_input( mut commands: Commands, time: Res