From 8f01e0bc80c3f9415c099a4ae21f93fbda14da20 Mon Sep 17 00:00:00 2001 From: demenik Date: Tue, 9 Dec 2025 17:30:55 +0100 Subject: [PATCH] feat: Implement shovel interaction and tile highlighting (#15) --- src/features/grid/mod.rs | 55 +++++++++++++++++++++++++- src/features/input/mod.rs | 82 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/src/features/grid/mod.rs b/src/features/grid/mod.rs index d2a064f..95ecebc 100644 --- a/src/features/grid/mod.rs +++ b/src/features/grid/mod.rs @@ -1,5 +1,6 @@ use crate::prelude::*; use components::{CropVisual, WaterVisual}; +use std::collections::HashSet; pub mod components; pub mod consts; @@ -88,7 +89,10 @@ fn cleanup(mut commands: Commands, tile_query: Query>) { } fn update_tiles( - mut query: Query<(&TileState, &mut AseSlice, &Children), (With, Without)>, + mut query: Query< + (&TileState, &mut AseSlice, &Children, &Tile), + (With, Without), + >, mut crop_query: Query< (&mut Visibility, &mut Transform, &mut AseSlice), (With, Without, Without), @@ -99,8 +103,27 @@ fn update_tiles( >, asset_server: Res, game_config: Res, + inventory: Res, + item_stacks: Query<&ItemStack>, + grid: Res, + mut sprite_query: Query<&mut Sprite, With>, ) { - for (state, mut slice, children) in &mut query { + let has_shovel = inventory.has_item_type(&item_stacks, ItemType::Shovel); + + let owned_tiles: HashSet<(u32, u32)> = query + .iter() + .filter_map(|(state, _, _, tile)| { + if !matches!(state, TileState::Unclaimed) { + Some((tile.x, tile.y)) + } else { + None + } + }) + .collect(); + + for (state, mut slice, children, tile) in &mut query { + let entity = grid.get_tile((tile.x, tile.y)).unwrap(); // Get entity for sprite query + slice.name = match state { TileState::Unclaimed => "Unclaimed", TileState::Empty => "Empty", @@ -133,6 +156,34 @@ fn update_tiles( _ => Vec3::ONE, }; + let mut is_highlighted = false; + if has_shovel && matches!(state, TileState::Unclaimed) { + // Check if not on edge + if tile.x > 0 && tile.x < grid.width - 1 && tile.y > 0 && tile.y < grid.height - 1 { + // Check neighbors + let neighbors = [ + (tile.x + 1, tile.y), + (tile.x.saturating_sub(1), tile.y), + (tile.x, tile.y + 1), + (tile.x, tile.y.saturating_sub(1)), + ]; + for n in neighbors.iter() { + if owned_tiles.contains(n) { + is_highlighted = true; + break; + } + } + } + } + + if let Ok(mut sprite) = sprite_query.get_mut(entity) { + if is_highlighted { + sprite.color = Color::srgb(0.3, 1.0, 0.3); // Green tint + } else { + sprite.color = Color::WHITE; + } + } + for child in children.iter() { if let Ok((mut visibility, mut transform, mut sprite)) = crop_query.get_mut(child) { *visibility = match state { diff --git a/src/features/input/mod.rs b/src/features/input/mod.rs index 0059c66..41c7225 100644 --- a/src/features/input/mod.rs +++ b/src/features/input/mod.rs @@ -53,7 +53,13 @@ fn move_click( config: Res, phase: Res, ui_query: Query<(&ComputedNode, &GlobalTransform), With>, + inventory: Res, + item_stacks: Query<&ItemStack>, ) { + if inventory.has_item_type(&item_stacks, ItemType::Shovel) { + return; // Block movement if player has a shovel + } + match phase.0 { Phase::Focus { .. } => return, _ => {} @@ -78,12 +84,25 @@ fn interact_click( config: Res, phase: Res, ui_query: Query<(&ComputedNode, &GlobalTransform), With>, + mut commands: Commands, + mut inventory: ResMut, + mut item_stacks: Query<&mut ItemStack>, + grid: Res, + mut tile_states: Query<&mut TileState>, ) { match phase.0 { Phase::Focus { .. } => return, _ => {} } + let has_shovel = inventory.items.iter().any(|&entity| { + if let Ok(stack) = item_stacks.get(entity) { + stack.item_type == ItemType::Shovel + } else { + false + } + }); + if mouse_btn.just_pressed(MouseButton::Left) { if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) { return; @@ -92,10 +111,69 @@ fn interact_click( return; }; - tile_click_messages.write(TileClickMessage { x, y }); + if has_shovel { + // Shovel interaction logic + let tile_entity = match grid.get_tile((x, y)) { + Ok(entity) => entity, + Err(_) => return, // Clicked outside grid + }; + + // Before getting mutable tile_state, check neighbors with immutable borrow + let mut has_claimed_neighbor = false; + // Check if not on edge first for early exit to avoid checking neighbors outside grid. + if x == 0 || x == grid.width - 1 || y == 0 || y == grid.height - 1 { + return; + } + + let neighbors = [ + (x + 1, y), + (x.saturating_sub(1), y), + (x, y + 1), + (x, y.saturating_sub(1)), + ]; + + for (nx, ny) in neighbors.iter() { + // Ensure neighbor coordinates are within grid boundaries before attempting to get tile + if *nx < grid.width && *ny < grid.height { + if let Ok(neighbor_entity) = grid.get_tile((*nx, *ny)) { + if let Ok(neighbor_state) = tile_states.get(neighbor_entity) { + if !matches!(*neighbor_state, TileState::Unclaimed) { + has_claimed_neighbor = true; + break; + } + } + } + } + } + + if !has_claimed_neighbor { + return; // No claimed neighbor, cannot unlock + } + + // Now get mutable tile_state, after all immutable neighbor checks are done + let mut tile_state = match tile_states.get_mut(tile_entity) { + Ok(state) => state, + Err(_) => return, // Should not happen + }; + + // Check if unclaimed AFTER determining neighbor status + if !matches!(*tile_state, TileState::Unclaimed) { + return; + } + + if has_claimed_neighbor { + // This check is redundant due to early return above, but kept for clarity + // Unlock tile + *tile_state = TileState::Empty; + inventory.update_item_stack(&mut commands, &mut item_stacks, ItemType::Shovel, -1); + } + return; // Consume click event, prevent normal tile_click_messages + } else { + // Normal interaction + tile_click_messages.write(TileClickMessage { x, y }); + } } } - fn debug_click( mouse_btn: Res>, keys: Res>,