From e990957ac067567c72f37def0297834f960b90b6 Mon Sep 17 00:00:00 2001 From: demenik Date: Mon, 1 Dec 2025 16:52:46 +0100 Subject: [PATCH 1/6] fix: Update toggle TileState debug bind to Shift + LMB instead of only LMB --- README.md | 2 +- src/features/input/mod.rs | 71 +++++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 73f0c50..f69b5e6 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ cargo run ### Hidden binds (Only available in the debug build) - `Shift + Enter`: Duration of the current phase is set to 3 seconds. -- `Left Mouse Button` on Tile: Rotate tile state. +- `Shift + Left Mouse Button` on Tile: Toggle tile state. - `Shift + Arrow Up`: Add one berry to your inventory - `Shift + Arrow Down`: Remove one berry from your inventory diff --git a/src/features/input/mod.rs b/src/features/input/mod.rs index 56d80ee..72fe940 100644 --- a/src/features/input/mod.rs +++ b/src/features/input/mod.rs @@ -21,6 +21,9 @@ impl Plugin for InputPlugin { interact_click.run_if(in_state(AppState::GameScreen)), ); + #[cfg(debug_assertions)] + app.add_systems(Update, debug_click.run_if(in_state(AppState::GameScreen))); + app.add_message::(); app.add_systems( Update, @@ -74,14 +77,12 @@ fn move_click( fn interact_click( mut interact_messages: MessageWriter, mouse_btn: Res>, + keys: Res>, window: Single<&Window, With>, camera: Single<(&Camera, &GlobalTransform), With>, config: Res, phase: Res, ui_query: Query<(&ComputedNode, &GlobalTransform), With>, - // for debug - grid: ResMut, - tile_query: Query<&mut TileState>, ) { match phase.0 { Phase::Focus { .. } => return, @@ -89,6 +90,9 @@ fn interact_click( } if mouse_btn.just_pressed(MouseButton::Left) { + if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) { + return; + } let (cam, cam_transform) = *camera; let Some(cursor_pos) = window.cursor_position() else { @@ -108,19 +112,58 @@ fn interact_click( println!("Interact Click: ({}, {})", x, y); interact_messages.write(InteractStartMessage { x, y }); + } +} - if cfg!(debug_assertions) { - grid.map_tile_state( - (x, y), - |state| match state { - TileState::Unclaimed => TileState::Empty, - TileState::Empty => TileState::Occupied, - TileState::Occupied => TileState::Unclaimed, - }, - tile_query, - ) - .unwrap_or_else(|_| ()); +fn debug_click( + mouse_btn: Res>, + keys: Res>, + window: Single<&Window, With>, + camera: Single<(&Camera, &GlobalTransform), With>, + config: Res, + phase: Res, + ui_query: Query<(&ComputedNode, &GlobalTransform), With>, + grid: Res, + tile_query: Query<&mut TileState>, +) { + match phase.0 { + Phase::Focus { .. } => return, + _ => {} + } + + if mouse_btn.just_pressed(MouseButton::Left) { + if !keys.pressed(KeyCode::ShiftLeft) && !keys.pressed(KeyCode::ShiftRight) { + return; } + + let (cam, cam_transform) = *camera; + + let Some(cursor_pos) = window.cursor_position() else { + return; + }; + if ui_blocks(window, cursor_pos, ui_query) { + return; + } + let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else { + return; + }; + let Ok((x, y)) = + world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height) + else { + return; + }; + + println!("Debug Toggle Click: ({}, {})", x, y); + grid.map_tile_state( + (x, y), + |state| match state { + TileState::Unclaimed => TileState::Empty, + TileState::Empty => TileState::Occupied, + TileState::Occupied => TileState::Unclaimed, + }, + tile_query, + ) + .unwrap_or_else(|_| ()); } } From e840e9a78b4c60fb498ad603927cdc2005c383b7 Mon Sep 17 00:00:00 2001 From: demenik Date: Mon, 1 Dec 2025 16:59:33 +0100 Subject: [PATCH 2/6] refactor: Move mouse click to grid position logic into utils.rs --- src/features/input/mod.rs | 49 +++++-------------------------------- src/features/input/utils.rs | 28 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 43 deletions(-) create mode 100644 src/features/input/utils.rs diff --git a/src/features/input/mod.rs b/src/features/input/mod.rs index 72fe940..449c7f0 100644 --- a/src/features/input/mod.rs +++ b/src/features/input/mod.rs @@ -1,4 +1,5 @@ use crate::features::{ + input::utils::mouse_to_grid, phase::messages::{NextPhaseMessage, PhaseTimerPauseMessage}, pom::messages::InvalidMoveMessage, shop::ui::open_shop, @@ -7,6 +8,8 @@ use crate::prelude::*; use bevy::input::mouse::MouseButton; use bevy::window::PrimaryWindow; +pub mod utils; + pub struct InputPlugin; impl Plugin for InputPlugin { @@ -52,20 +55,7 @@ fn move_click( } if mouse_btn.just_pressed(MouseButton::Right) { - let (cam, cam_transform) = *camera; - - let Some(cursor_pos) = window.cursor_position() else { - return; - }; - if ui_blocks(window, cursor_pos, ui_query) { - return; - } - let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else { - return; - }; - let Ok((x, y)) = - world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height) - else { + let Some((x, y)) = mouse_to_grid(window, camera, config, ui_query) else { return; }; @@ -93,20 +83,7 @@ fn interact_click( if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) { return; } - let (cam, cam_transform) = *camera; - - let Some(cursor_pos) = window.cursor_position() else { - return; - }; - if ui_blocks(window, cursor_pos, ui_query) { - return; - } - let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else { - return; - }; - let Ok((x, y)) = - world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height) - else { + let Some((x, y)) = mouse_to_grid(window, camera, config, ui_query) else { return; }; @@ -135,21 +112,7 @@ fn debug_click( if !keys.pressed(KeyCode::ShiftLeft) && !keys.pressed(KeyCode::ShiftRight) { return; } - - let (cam, cam_transform) = *camera; - - let Some(cursor_pos) = window.cursor_position() else { - return; - }; - if ui_blocks(window, cursor_pos, ui_query) { - return; - } - let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else { - return; - }; - let Ok((x, y)) = - world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height) - else { + let Some((x, y)) = mouse_to_grid(window, camera, config, ui_query) else { return; }; diff --git a/src/features/input/utils.rs b/src/features/input/utils.rs new file mode 100644 index 0000000..7f38b6e --- /dev/null +++ b/src/features/input/utils.rs @@ -0,0 +1,28 @@ +use crate::prelude::*; +use bevy::window::PrimaryWindow; + +pub fn mouse_to_grid( + window: Single<&Window, With>, + camera: Single<(&Camera, &GlobalTransform), With>, + config: Res, + ui_query: Query<(&ComputedNode, &GlobalTransform), With>, +) -> Option<(u32, u32)> { + let (cam, cam_transform) = *camera; + + let Some(cursor_pos) = window.cursor_position() else { + return None; + }; + if ui_blocks(window, cursor_pos, ui_query) { + return None; + } + let Ok(world_pos) = cam.viewport_to_world(cam_transform, cursor_pos) else { + return None; + }; + let Ok(grid_pos) = + world_to_grid_coords(world_pos.origin, config.grid_width, config.grid_height) + else { + return None; + }; + + Some(grid_pos) +} From 15ea976442a572683dbf1d7e5405818887fd6d4c Mon Sep 17 00:00:00 2001 From: demenik Date: Mon, 1 Dec 2025 17:05:51 +0100 Subject: [PATCH 3/6] feat: Implement interact click logic (#25) --- src/features/pom/components.rs | 5 ++ src/features/pom/mod.rs | 102 +++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/features/pom/components.rs b/src/features/pom/components.rs index 7cf4043..089438a 100644 --- a/src/features/pom/components.rs +++ b/src/features/pom/components.rs @@ -36,3 +36,8 @@ impl MovingState { ) } } + +#[derive(Component, Default)] +pub struct InteractionTarget { + pub target: Option<(u32, u32)>, +} diff --git a/src/features/pom/mod.rs b/src/features/pom/mod.rs index ce4a7d5..aaa1e74 100644 --- a/src/features/pom/mod.rs +++ b/src/features/pom/mod.rs @@ -1,7 +1,9 @@ use crate::prelude::*; use components::*; -use messages::InvalidMoveMessage; +use messages::{InteractStartMessage, InvalidMoveMessage}; +use std::collections::VecDeque; use utils::find_path; +use utils::manhattan_distance; pub mod components; pub mod messages; @@ -16,7 +18,14 @@ impl Plugin for PomPlugin { app.add_systems( Update, - (handle_move, move_pom, update_pom).run_if(in_state(AppState::GameScreen)), + ( + handle_move, + handle_interact, + move_pom, + update_pom, + perform_interaction, + ) + .run_if(in_state(AppState::GameScreen)), ); } } @@ -27,6 +36,7 @@ fn setup(mut commands: Commands, asset_server: Res, config: Res, grid: Res, tile_query: Query<&TileState>, - mut pom_query: Query<(&GridPosition, &mut PathQueue)>, + mut pom_query: Query<(&GridPosition, &mut PathQueue, &mut InteractionTarget)>, ) { for message in move_messages.read() { - for (grid_pos, mut path_queue) in pom_query.iter_mut() { + for (grid_pos, mut path_queue, mut interaction_target) in pom_query.iter_mut() { + // Clear any pending interaction when moving manually + interaction_target.target = None; + let grid_start = (grid_pos.x, grid_pos.y); let start = path_queue.steps.front().unwrap_or(&grid_start); let end = (message.x, message.y); @@ -78,6 +91,86 @@ fn handle_move( } } +fn handle_interact( + mut interact_messages: MessageReader, + mut pom_query: Query<(&GridPosition, &mut PathQueue, &mut InteractionTarget)>, + grid: Res, + tile_query: Query<&TileState>, +) { + for message in interact_messages.read() { + for (grid_pos, mut path_queue, mut interaction_target) in pom_query.iter_mut() { + let target_pos = (message.x, message.y); + let current_pos = (grid_pos.x, grid_pos.y); + + // If we are already adjacent to the target, just set the target and clear path + if manhattan_distance(current_pos.0, current_pos.1, target_pos.0, target_pos.1) == 1 { + path_queue.steps.clear(); + interaction_target.target = Some(target_pos); + continue; + } + + // Find a path to an adjacent tile + let neighbors = [ + (target_pos.0 as i32 + 1, target_pos.1 as i32), + (target_pos.0 as i32 - 1, target_pos.1 as i32), + (target_pos.0 as i32, target_pos.1 as i32 + 1), + (target_pos.0 as i32, target_pos.1 as i32 - 1), + ]; + + let mut best_path: Option> = None; + + for (nx, ny) in neighbors { + if nx < 0 || ny < 0 { + continue; + } + let neighbor_pos = (nx as u32, ny as u32); + + if let Some(path) = find_path(current_pos, neighbor_pos, &grid, &tile_query) { + // Pick the shortest path + if best_path.as_ref().map_or(true, |p| path.len() < p.len()) { + best_path = Some(path); + } + } + } + + if let Some(path) = best_path { + path_queue.steps = path; + interaction_target.target = Some(target_pos); + } else { + println!("Cannot reach interaction target at {:?}", target_pos); + // Don't set target if unreachable + interaction_target.target = None; + } + } + } +} + +fn perform_interaction( + mut pom_query: Query<(&GridPosition, &mut InteractionTarget, &PathQueue)>, + // grid: Res, + // mut tile_query: Query<&mut TileState>, +) { + for (pos, mut target_component, path_queue) in pom_query.iter_mut() { + if let Some(target) = target_component.target { + // Wait until movement stops + if !path_queue.steps.is_empty() { + continue; + } + + if manhattan_distance(pos.x, pos.y, target.0, target.1) == 1 { + println!( + "Performing interaction on tile ({}, {})", + target.0, target.1 + ); + + // TODO: Implement interaction logic + } + + target_component.target = None; + } + } +} + fn move_pom( time: Res