diff --git a/src/inventory/item.rs b/src/inventory/item.rs new file mode 100644 index 0000000..6c32889 --- /dev/null +++ b/src/inventory/item.rs @@ -0,0 +1,35 @@ +use bevy::prelude::*; + +#[derive(Component, Clone)] +pub struct Item { + pub size: UVec2, + pub position: Option, +} + +impl Item { + pub fn new(size: UVec2) -> Self { + Self { size, position: None } + } + + pub fn new_positioned(size: UVec2, position: UVec2) -> Self { + Self { size, position: Some(position) } + } + + pub fn rect(&self) -> Option { + let Some(position) = self.position else { + return None; + }; + + Some(URect::from_corners(position, position + self.size)) + } + + pub fn overlaps(&self, other_size: UVec2, other_position: UVec2) -> bool { + let Some(rect) = self.rect() else { + return false; + }; + + let other_rect = URect::from_corners(other_position, other_position + other_size); + + !rect.intersect(other_rect).is_empty() + } +} diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs new file mode 100644 index 0000000..e929f6b --- /dev/null +++ b/src/inventory/mod.rs @@ -0,0 +1,39 @@ +use bevy::prelude::*; + +pub mod item; + +#[derive(Component)] +pub struct Inventory { + pub size: UVec2, +} + +impl Inventory { + pub fn new(size: UVec2) -> Self { + Self { size } + } + + pub fn can_fit( + &self, + item_query: Query<&item::Item>, + contained_items: &Children, + queried_size: UVec2, + queried_position: UVec2, + ) -> bool { + let item_corner = queried_size + queried_position; + if item_corner.x > self.size.x || item_corner.y > self.size.y { + return false; + } + + for entity in contained_items { + let Ok(item) = item_query.get(*entity) else { + warn!("Could not query inventory child ({entity}), probably not item?"); + continue; + }; + + if item.overlaps(queried_size, queried_position) { + return false; + } + } + true + } +} diff --git a/src/lib.rs b/src/lib.rs index bc30edc..1e74ec8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod player; pub mod layout; pub mod input; +pub mod inventory; #[cfg(test)] mod tests; diff --git a/src/tests.rs b/src/tests.rs index dfa0b72..9f494c6 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,76 +1,189 @@ -use super::*; -use leafwing_input_manager::prelude::*; +mod input { + use super::super::*; + use leafwing_input_manager::prelude::*; + #[derive(Actionlike, Reflect, Clone, Debug, PartialEq, Eq, Hash, Default)] + enum Action { + #[default] + #[actionlike(DualAxis)] + DualAxis, + #[actionlike(Axis)] + SingleAxis, + Button, + } -#[derive(Actionlike, Reflect, Clone, Debug, PartialEq, Eq, Hash, Default)] -enum Action { - #[default] - #[actionlike(DualAxis)] - DualAxis, - #[actionlike(Axis)] - SingleAxis, - Button, + #[test] + fn asset_from_map() { + let mut input_map = InputMap::default(); + input_map.insert(Action::Button, KeyCode::KeyE); + input_map.insert(Action::Button, GamepadButton::East); + + input_map.insert_axis(Action::SingleAxis, GamepadAxis::LeftStickX); + input_map.insert_axis(Action::SingleAxis, VirtualAxis::ad()); + + input_map.insert_dual_axis(Action::DualAxis, GamepadStick::RIGHT); + input_map.insert_dual_axis(Action::DualAxis, VirtualDPad::wasd()); + + let mut expected_input_asset = input::InputAsset::default(); + expected_input_asset.insert(Action::Button, input::MultiInput { + keyboard: Some(vec![KeyCode::KeyE]), + gamepad: Some(vec![GamepadButton::East]), + mouse: None, + }.into()); + expected_input_asset.insert(Action::SingleAxis, input::InputKind::Axis(vec![ + Box::new(GamepadAxis::LeftStickX), + Box::new(VirtualAxis::ad()), + ])); + expected_input_asset.insert(Action::DualAxis, input::InputKind::DualAxis(vec![ + Box::new(GamepadStick::RIGHT), + Box::new(VirtualDPad::wasd()), + ])); + + let input_asset = input::InputAsset::from(input_map); + + assert_eq!(input_asset, expected_input_asset); + } + + #[test] + fn map_from_asset() { + let mut input_asset = input::InputAsset::default(); + input_asset.insert(Action::Button, input::MultiInput { + keyboard: Some(vec![KeyCode::KeyE]), + gamepad: Some(vec![GamepadButton::East]), + mouse: None, + }.into()); + input_asset.insert(Action::SingleAxis, input::InputKind::Axis(vec![ + Box::new(GamepadAxis::LeftStickX), + Box::new(VirtualAxis::ad()), + ])); + input_asset.insert(Action::DualAxis, input::InputKind::DualAxis(vec![ + Box::new(GamepadStick::RIGHT), + Box::new(VirtualDPad::wasd()), + ])); + + let mut expected_input_map = InputMap::default(); + expected_input_map.insert(Action::Button, KeyCode::KeyE); + expected_input_map.insert(Action::Button, GamepadButton::East); + + expected_input_map.insert_axis(Action::SingleAxis, GamepadAxis::LeftStickX); + expected_input_map.insert_axis(Action::SingleAxis, VirtualAxis::ad()); + + expected_input_map.insert_dual_axis(Action::DualAxis, GamepadStick::RIGHT); + expected_input_map.insert_dual_axis(Action::DualAxis, VirtualDPad::wasd()); + + let input_map = InputMap::from(input_asset); + + assert_eq!(input_map, expected_input_map); + } } -#[test] -fn input_asset_from_map() { - let mut input_map = InputMap::default(); - input_map.insert(Action::Button, KeyCode::KeyE); - input_map.insert(Action::Button, GamepadButton::East); +mod inventory { + use bevy::prelude::*; + use crate::inventory::{Inventory, item::Item}; + + fn inventory() -> Inventory { + Inventory::new(UVec2::splat(4)) + } - input_map.insert_axis(Action::SingleAxis, GamepadAxis::LeftStickX); - input_map.insert_axis(Action::SingleAxis, VirtualAxis::ad()); + fn item_a() -> Item { + Item::new_positioned(UVec2::new(1, 2), UVec2::new(0, 0)) + } + + fn item_b() -> Item { + Item::new_positioned(UVec2::new(2, 2), UVec2::new(0, 2)) + } + + fn item_c() -> Item { + Item::new_positioned(UVec2::new(3, 2), UVec2::new(1, 0)) + } + + fn item_d() -> Item { + Item::new_positioned(UVec2::new(2, 2), UVec2::new(2, 2)) + } - input_map.insert_dual_axis(Action::DualAxis, GamepadStick::RIGHT); - input_map.insert_dual_axis(Action::DualAxis, VirtualDPad::wasd()); + fn item_e() -> Item { + Item::new_positioned(UVec2::new(1, 1), UVec2::new(0, 0)) + } - let mut expected_input_asset = input::InputAsset::default(); - expected_input_asset.insert(Action::Button, input::MultiInput { - keyboard: Some(vec![KeyCode::KeyE]), - gamepad: Some(vec![GamepadButton::East]), - mouse: None, - }.into()); - expected_input_asset.insert(Action::SingleAxis, input::InputKind::Axis(vec![ - Box::new(GamepadAxis::LeftStickX), - Box::new(VirtualAxis::ad()), - ])); - expected_input_asset.insert(Action::DualAxis, input::InputKind::DualAxis(vec![ - Box::new(GamepadStick::RIGHT), - Box::new(VirtualDPad::wasd()), - ])); + fn item_f() -> Item { + Item::new_positioned(UVec2::new(5, 5), UVec2::new(0, 0)) + } - let input_asset = input::InputAsset::from(input_map); + fn item_g() -> Item { + Item::new_positioned(UVec2::new(2, 2), UVec2::new(3, 3)) + } - assert_eq!(input_asset, expected_input_asset); -} - -#[test] -fn input_map_from_asset() { - let mut input_asset = input::InputAsset::default(); - input_asset.insert(Action::Button, input::MultiInput { - keyboard: Some(vec![KeyCode::KeyE]), - gamepad: Some(vec![GamepadButton::East]), - mouse: None, - }.into()); - input_asset.insert(Action::SingleAxis, input::InputKind::Axis(vec![ - Box::new(GamepadAxis::LeftStickX), - Box::new(VirtualAxis::ad()), - ])); - input_asset.insert(Action::DualAxis, input::InputKind::DualAxis(vec![ - Box::new(GamepadStick::RIGHT), - Box::new(VirtualDPad::wasd()), - ])); - - let mut expected_input_map = InputMap::default(); - expected_input_map.insert(Action::Button, KeyCode::KeyE); - expected_input_map.insert(Action::Button, GamepadButton::East); - - expected_input_map.insert_axis(Action::SingleAxis, GamepadAxis::LeftStickX); - expected_input_map.insert_axis(Action::SingleAxis, VirtualAxis::ad()); - - expected_input_map.insert_dual_axis(Action::DualAxis, GamepadStick::RIGHT); - expected_input_map.insert_dual_axis(Action::DualAxis, VirtualDPad::wasd()); - - let input_map = InputMap::from(input_asset); - - assert_eq!(input_map, expected_input_map); + #[derive(Resource)] + struct Items(Vec, usize); + + fn insert_item( + mut commands: Commands, + mut items: ResMut, + item_query: Query<&Item>, + inventory_query: Query<(Entity, &Inventory, Option<&Children>)> + ) { + let item = &items.0[items.1]; + let q_size = item.size; + let q_pos = item.position.unwrap(); + let (entity, inventory, children) = inventory_query.single().unwrap(); + if let Some(children) = children { + println!("{q_size} {q_pos}"); + assert!(inventory.can_fit(item_query, children, q_size, q_pos)); + } + let item_entity = commands.spawn(item.clone()).id(); + commands.entity(entity).add_child(item_entity); + items.1 += 1; + } + + fn failed_insert_item( + mut commands: Commands, + mut items: ResMut, + item_query: Query<&Item>, + inventory_query: Query<(Entity, &Inventory, Option<&Children>)> + ) { + let item = &items.0[items.1]; + let q_size = item.size; + let q_pos = item.position.unwrap(); + let (entity, inventory, children) = inventory_query.single().unwrap(); + if let Some(children) = children { + println!("{q_size} {q_pos}"); + assert!(!inventory.can_fit(item_query, children, q_size, q_pos)); + } + let item_entity = commands.spawn(item.clone()).id(); + commands.entity(entity).add_child(item_entity); + items.1 += 1; + } + + #[test] + fn everything_fits() { + let mut world = World::new(); + + let system = world.register_system(insert_item); + + world.insert_resource(Items(vec![item_a(), item_b(), item_c(), item_d()], 0)); + + world.spawn(inventory()); + + for _ in 0..4 { + world.run_system(system).expect("Error on running system"); + } + } + + #[test] + fn items_e_f_g_do_not_fit() { + let mut world = World::new(); + + let system = world.register_system(failed_insert_item); + + world.insert_resource(Items(vec![item_e(), item_f(), item_g()], 0)); + + world.spawn(inventory()) + .with_child(item_a()) + .with_child(item_b()) + .with_child(item_c()) + .with_child(item_d()); + + for _ in 0..3 { + world.run_system(system).expect("Error on running system"); + } + } }