From 0a0eb14a6b0087d35dc609443fd711978a3d35c7 Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Thu, 19 Mar 2026 15:01:04 +0300 Subject: [PATCH 1/2] feat: input plugin reflects, derives --- Cargo.lock | 2 +- src/input/plugin.rs | 48 +++++++++++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06b99c8..8b4d53a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2422,7 +2422,7 @@ dependencies = [ [[package]] name = "expedition_demo" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bevy", "bevy_common_assets", diff --git a/src/input/plugin.rs b/src/input/plugin.rs index 30052c1..51f6de1 100644 --- a/src/input/plugin.rs +++ b/src/input/plugin.rs @@ -1,13 +1,33 @@ -use std::{any::{Any, TypeId}, collections::HashMap, hash::Hash, marker::PhantomData}; +use std::{ + any::{ + Any, + TypeId, + }, + collections::HashMap, + hash::Hash, + marker::PhantomData, +}; -use bevy::{prelude::*, reflect::GetTypeRegistration}; +use bevy::{ + prelude::*, + reflect::GetTypeRegistration, +}; use bevy_common_assets::toml::TomlAssetPlugin; -use leafwing_input_manager::{Actionlike, plugin::InputManagerPlugin, prelude::*}; -use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use leafwing_input_manager::{ + Actionlike, + plugin::InputManagerPlugin, + prelude::*, +}; +use serde::{ + Deserialize, + Serialize, + de::DeserializeOwned, +}; const INPUT_ASSET_EXTENSIONS: [&'static str; 1] = ["input.toml"]; -#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Reflect)] +#[reflect(Clone, Debug, Serialize, Deserialize, Default, PartialEq)] pub struct MultiInput { pub keyboard: Option>, pub mouse: Option>, @@ -20,7 +40,8 @@ impl From for InputKind { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Reflect)] +#[reflect(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(untagged)] pub enum InputKind { Button(MultiInput), @@ -29,6 +50,7 @@ pub enum InputKind { } #[derive(Default, Deref, DerefMut, Debug, Asset, Reflect, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[reflect(Debug, Clone, PartialEq)] pub struct InputAsset where Name: Sized + Hash + Eq + Reflect + TypePath + Actionlike { #[serde(flatten)] @@ -205,17 +227,18 @@ impl From> for InputAsset } } -#[derive(Resource, Deref)] +#[derive(Resource, Debug, Deref, DerefMut, Reflect, Clone, PartialEq, Eq)] +#[reflect(Resource, Debug, Clone, PartialEq)] pub struct InputAssetHandle (Option>>); #[derive(Debug)] -pub struct InputAssetPlugin { +pub struct InputAssetPlugin { _phantom: PhantomData, extensions: &'static [&'static str], } impl InputAssetPlugin - where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration { + where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned { pub fn new(extensions: &'static [&'static str]) -> Self { Self { _phantom: PhantomData, @@ -225,7 +248,7 @@ impl InputAssetPlugin } impl Default for InputAssetPlugin - where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration { + where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned { fn default() -> Self { Self { _phantom: PhantomData, @@ -235,12 +258,11 @@ impl Default for InputAssetPlugin } impl Plugin for InputAssetPlugin - where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration -{ + where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration { fn build(&self, app: &mut App) { app.add_plugins(( + InputManagerPlugin::::default(), TomlAssetPlugin::>::new(&self.extensions), - InputManagerPlugin::::default() )); } } From ffdb5d94a85d1fcccf74dbadb43809249303862d Mon Sep 17 00:00:00 2001 From: 2ndbeam <2ndbeam@disroot.org> Date: Thu, 19 Mar 2026 15:57:29 +0300 Subject: [PATCH 2/2] feat: Derive traits --- src/input/mod.rs | 4 ++- src/input/plugin.rs | 12 ++++---- src/inventory/item.rs | 3 +- src/inventory/mod.rs | 46 ++++++++++++++-------------- src/layout/container.rs | 7 +++-- src/layout/door.rs | 9 +++++- src/layout/mod.rs | 12 +++++--- src/lib.rs | 66 ++++++++++++++++++++++------------------- src/player/mod.rs | 13 +++++--- src/ui/inventory/mod.rs | 21 ++++++++----- src/ui/mod.rs | 3 +- 11 files changed, 116 insertions(+), 80 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 5145335..1960295 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,13 +1,14 @@ use bevy::prelude::*; use leafwing_input_manager::prelude::*; use serde::{ + Deserialize, Serialize, - Deserialize }; pub mod plugin; #[derive(Actionlike, PartialEq, Eq, Hash, Debug, Clone, Reflect, Serialize, Deserialize)] +#[reflect(PartialEq, Hash, Debug, Clone, Serialize, Deserialize)] pub enum InputAction { #[actionlike(Axis)] Move, @@ -28,6 +29,7 @@ impl InputAction { } #[derive(Actionlike, PartialEq, Eq, Hash, Debug, Clone, Reflect, Serialize, Deserialize)] +#[reflect(PartialEq, Hash, Debug, Clone, Serialize, Deserialize)] pub enum UiAction { Rotate, } diff --git a/src/input/plugin.rs b/src/input/plugin.rs index 51f6de1..d0f8b14 100644 --- a/src/input/plugin.rs +++ b/src/input/plugin.rs @@ -10,7 +10,7 @@ use std::{ use bevy::{ prelude::*, - reflect::GetTypeRegistration, + reflect::Reflectable, }; use bevy_common_assets::toml::TomlAssetPlugin; use leafwing_input_manager::{ @@ -229,16 +229,16 @@ impl From> for InputAsset #[derive(Resource, Debug, Deref, DerefMut, Reflect, Clone, PartialEq, Eq)] #[reflect(Resource, Debug, Clone, PartialEq)] -pub struct InputAssetHandle (Option>>); +pub struct InputAssetHandle (Option>>); #[derive(Debug)] -pub struct InputAssetPlugin { +pub struct InputAssetPlugin { _phantom: PhantomData, extensions: &'static [&'static str], } impl InputAssetPlugin - where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned { + where T: Sized + Hash + Eq + Reflectable + Actionlike + DeserializeOwned { pub fn new(extensions: &'static [&'static str]) -> Self { Self { _phantom: PhantomData, @@ -248,7 +248,7 @@ impl InputAssetPlugin } impl Default for InputAssetPlugin - where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned { + where T: Sized + Hash + Eq + Reflectable + Actionlike + DeserializeOwned { fn default() -> Self { Self { _phantom: PhantomData, @@ -258,7 +258,7 @@ impl Default for InputAssetPlugin } impl Plugin for InputAssetPlugin - where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration { + where T: Sized + Hash + Eq + Reflectable + Actionlike + DeserializeOwned { fn build(&self, app: &mut App) { app.add_plugins(( InputManagerPlugin::::default(), diff --git a/src/inventory/item.rs b/src/inventory/item.rs index e3523b1..d61a1d4 100644 --- a/src/inventory/item.rs +++ b/src/inventory/item.rs @@ -2,7 +2,8 @@ use std::mem::swap; use bevy::prelude::*; -#[derive(Component, Clone, Debug, Reflect)] +#[derive(Component, Clone, Debug, Reflect, PartialEq, Eq)] +#[reflect(Component, Clone, Debug, PartialEq)] pub struct Item { pub size: UVec2, pub position: Option, diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index 0cf99f7..88d15a7 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -2,15 +2,17 @@ use bevy::prelude::*; pub mod item; -#[derive(Component, Reflect, Default)] +/// Marker that this inventory will show up when UI is built +#[derive(Component, Reflect, Default, Clone, Copy, Debug, PartialEq, Eq)] +#[reflect(Component, Clone, Default, Debug, PartialEq)] +pub struct ActiveInventory; + +#[derive(Component, Reflect, Default, Clone, Copy, Debug, PartialEq, Eq)] +#[reflect(Component, Clone, Default, Debug, PartialEq)] pub struct Inventory { pub size: UVec2, } -/// Marker that this inventory will show up when UI is built -#[derive(Component, Reflect)] -pub struct ActiveInventory; - impl Inventory { pub fn new(size: UVec2) -> Self { Self { size } @@ -58,6 +60,23 @@ impl Inventory { self.can_fit(item_query, children.as_slice(), *size, queried_position) } + pub fn can_replace( + &self, + item_query: Query<&item::Item>, + contained_items: &[Entity], + replacable_item: Entity, + queried_item: &item::Item, + ) -> bool { + let Some(position) = &queried_item.position else { + warn!("Trying to query rotated item without position"); + return false; + }; + let children = contained_items.iter() + .filter_map(|e| if e.ne(&&replacable_item) { Some(*e) } else { None }) + .collect::>(); + self.can_fit(item_query, children.as_slice(), queried_item.size, *position) + } + pub fn can_rotate( &self, item_query: Query<&item::Item>, @@ -79,23 +98,6 @@ impl Inventory { self.can_fit(item_query, children.as_slice(), rotated_item.size, *position) } - pub fn can_replace( - &self, - item_query: Query<&item::Item>, - contained_items: &[Entity], - replacable_item: Entity, - queried_item: &item::Item, - ) -> bool { - let Some(position) = &queried_item.position else { - warn!("Trying to query rotated item without position"); - return false; - }; - let children = contained_items.iter() - .filter_map(|e| if e.ne(&&replacable_item) { Some(*e) } else { None }) - .collect::>(); - self.can_fit(item_query, children.as_slice(), queried_item.size, *position) - } - fn find_free_space_inner( &self, item_query: Query<&item::Item>, diff --git a/src/layout/container.rs b/src/layout/container.rs index 5236a26..2c0019e 100644 --- a/src/layout/container.rs +++ b/src/layout/container.rs @@ -11,11 +11,12 @@ use crate::{ }, }; -const CRATE_CLOSED_ASSET: &'static str = "sprites/interactive/crate_closed.png"; - use super::*; -#[derive(Component)] +const CRATE_CLOSED_ASSET: &'static str = "sprites/interactive/crate_closed.png"; + +#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)] +#[reflect(Component, Clone, Default, Debug, PartialEq)] #[require(Sprite, InteractiveObject, Inventory)] pub struct Container; diff --git a/src/layout/door.rs b/src/layout/door.rs index fac9ea3..ddd849f 100644 --- a/src/layout/door.rs +++ b/src/layout/door.rs @@ -8,10 +8,17 @@ use super::*; const DOOR_OPENED_ASSET: &'static str = "sprites/interactive/door_opened.png"; const DOOR_CLOSED_ASSET: &'static str = "sprites/interactive/door_closed.png"; -#[derive(Component)] +#[derive(Component, Clone, Copy, Reflect, PartialEq, Eq, Debug)] +#[reflect(Component, Clone, Default, PartialEq, Debug)] #[require(Sprite, InteractiveObject)] pub struct Door(pub i8); +impl Default for Door { + fn default() -> Self { + Self(1) + } +} + fn on_door_interact( event: On, mut commands: Commands, diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 3a7a3f9..6a55002 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -4,16 +4,20 @@ pub mod container; pub mod door; pub mod systems; -#[derive(Component)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] pub struct MayInteract; -#[derive(Component, Default)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] pub struct InteractiveObject; -#[derive(Component)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] pub struct Locked; -#[derive(EntityEvent)] +#[derive(EntityEvent, Reflect, Clone, Copy, PartialEq, Eq, Debug)] +#[reflect(Event, Debug, PartialEq, Clone)] pub struct InteractionEvent { pub entity: Entity, } diff --git a/src/lib.rs b/src/lib.rs index 6332bca..9382ba8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,37 +1,32 @@ use bevy::{ prelude::*, - ui_widgets::ScrollbarPlugin + ui_widgets::ScrollbarPlugin, }; use bevy_rapier2d::{ prelude::*, - rapier::prelude::IntegrationParameters + rapier::prelude::IntegrationParameters, }; -pub mod player; -pub mod layout; pub mod input; pub mod inventory; -pub mod ui; +pub mod layout; +pub mod player; #[cfg(test)] mod tests; +pub mod ui; pub const PIXELS_PER_METER: f32 = 16.0; pub struct ExpeditionPlugin; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States, Reflect)] +#[reflect(Clone, PartialEq, Debug, Default, Hash, State)] pub enum GameState { #[default] Running, Inventory, } -pub fn insert_entity_name(names: Query<(Entity, &mut Name), Added>) { - for (entity, mut name) in names { - name.mutate(|name| name.insert_str(0, format!("{entity}: ").as_str())); - } -} - fn camera_bundle() -> impl Bundle { ( Camera2d, @@ -50,6 +45,12 @@ fn camera_bundle() -> impl Bundle { ) } +fn insert_entity_name(names: Query<(Entity, &mut Name), Added>) { + for (entity, mut name) in names { + name.mutate(|name| name.insert_str(0, format!("{entity}: ").as_str())); + } +} + fn setup_global(mut commands: Commands) { commands.spawn(camera_bundle()); commands.spawn(ui::UiRoot::new()); @@ -57,34 +58,39 @@ fn setup_global(mut commands: Commands) { impl Plugin for ExpeditionPlugin { fn build(&self, app: &mut App) { + let rapier_init = RapierContextInitialization::InitializeDefaultRapierContext { + integration_parameters: IntegrationParameters { + length_unit: PIXELS_PER_METER, + ..default() + }, + rapier_configuration: RapierConfiguration { + gravity: Vec2::ZERO, + physics_pipeline_active: true, + scaled_shape_subdivision: 10, + force_update_from_transform_changes: false, + }, + }; app.add_plugins(( + RapierDebugRenderPlugin::default(), + RapierPhysicsPlugin::<()>::default() + .with_custom_initialization(rapier_init), + ScrollbarPlugin, input::plugin::InputAssetPlugin::::default(), input::plugin::InputAssetPlugin::::default(), - ScrollbarPlugin, - RapierPhysicsPlugin::<()>::default() - .with_custom_initialization(RapierContextInitialization::InitializeDefaultRapierContext { - integration_parameters: IntegrationParameters { - length_unit: PIXELS_PER_METER, - ..default() - }, - rapier_configuration: RapierConfiguration { - gravity: Vec2::ZERO, - physics_pipeline_active: true, - scaled_shape_subdivision: 10, - force_update_from_transform_changes: false, - }, - }), - RapierDebugRenderPlugin::default(), )) .init_state::() .insert_resource(ui::WindowSize::default()) - .add_systems(Startup, (player::systems::setup_player, setup_global, layout::systems::setup_world)) + .add_systems(Startup, ( + setup_global, + layout::systems::setup_world, + player::systems::setup_player, + )) .add_systems(Update, ( + insert_entity_name, + layout::systems::detect_interact_collisions, player::systems::handle_input, ui::update_window_size, ui::handle_input, - insert_entity_name, - layout::systems::detect_interact_collisions, )) .add_systems(OnEnter(GameState::Inventory), ui::inventory::systems::setup_ui_inventory) .add_systems(OnExit(GameState::Inventory), ui::inventory::systems::clear_ui_inventory) diff --git a/src/player/mod.rs b/src/player/mod.rs index 3409370..841d711 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -9,18 +9,23 @@ use crate::{ pub mod systems; -#[derive(Component, Reflect)] +#[derive(Component, Clone, Copy, Reflect, PartialEq, Debug)] +#[reflect(Component, Clone, Default, PartialEq, Debug)] pub struct Player { // px/s speed: f32, } +impl Default for Player { + fn default() -> Self { + Self { speed: PIXELS_PER_METER * 0.8 } + } +} + pub fn player_bundle(asset_server: &Res) -> impl Bundle { let image = asset_server.load("sprites/player/player.png"); ( - Player { - speed: PIXELS_PER_METER * 0.8, - }, + Player::default(), Sprite::from_image(image), Transform::from_xyz(0f32, 0f32, 1f32), Action::default_input_map(), diff --git a/src/ui/inventory/mod.rs b/src/ui/inventory/mod.rs index 3d81fc3..3e81139 100644 --- a/src/ui/inventory/mod.rs +++ b/src/ui/inventory/mod.rs @@ -14,29 +14,36 @@ pub mod bundles; pub mod observers; pub mod systems; -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] #[require(Node)] pub struct UiInventoryManager; -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Clone)] #[require(Node)] pub struct UiInventory(pub Entity); -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] #[require(Node, ImageNode)] pub struct UiInventorySlot(pub UVec2); -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Clone)] #[require(Node, ImageNode)] pub struct UiItem(pub Entity); -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] pub struct HoveredItem; -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)] +#[reflect(Component, Debug, PartialEq, Default, Clone)] pub struct HoveredSlot; -#[derive(Component, Reflect)] +#[derive(Component, Debug, PartialEq, Eq, Clone, Reflect)] +#[reflect(Component, Debug, PartialEq, Clone)] pub struct DraggedItem(pub Item, pub UVec2); fn ui_item_node_data(item: &Item) -> (Val, Val, Val, Val, UiTransform) { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b3d338b..e67f792 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -8,8 +8,9 @@ use crate::input::UiAction; pub mod inventory; -#[derive(Component, Reflect)] +#[derive(Component, Reflect, Debug, Default, PartialEq, Eq, Clone, Copy)] #[require(Node)] +#[reflect(Component, Debug, Default, PartialEq, Clone)] pub struct UiRoot; #[derive(Resource, Deref, DerefMut, Default)]