From 4df968af2e6c839a1e97081fb696886a6dc51ed8 Mon Sep 17 00:00:00 2001 From: demenik Date: Tue, 2 Dec 2025 15:46:48 +0100 Subject: [PATCH] feat: Add watering (#27) --- src/features/grid/components.rs | 4 ++ src/features/grid/mod.rs | 22 ++++++++- src/features/input/mod.rs | 1 + src/features/phase/mod.rs | 19 +++++++ src/features/pom/actions.rs | 24 +++++++++ tests/interaction.rs | 88 ++++++++++++++++++++++++++++++++- tests/pathfinding.rs | 6 ++- 7 files changed, 159 insertions(+), 5 deletions(-) diff --git a/src/features/grid/components.rs b/src/features/grid/components.rs index a9a62a1..d2e9729 100644 --- a/src/features/grid/components.rs +++ b/src/features/grid/components.rs @@ -10,6 +10,9 @@ pub struct Tile { #[derive(Component)] pub struct CropVisual; +#[derive(Component)] +pub struct WaterVisual; + #[derive(Component, Default, Serialize, Deserialize, Clone, Debug)] pub enum TileState { #[default] @@ -17,6 +20,7 @@ pub enum TileState { Empty, Occupied { seed: ItemType, + watered: bool, }, } diff --git a/src/features/grid/mod.rs b/src/features/grid/mod.rs index ba20b81..3b769e5 100644 --- a/src/features/grid/mod.rs +++ b/src/features/grid/mod.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use components::CropVisual; +use components::{CropVisual, WaterVisual}; pub mod components; pub mod consts; @@ -55,6 +55,17 @@ fn setup(mut commands: Commands, asset_server: Res, config: Res>) { fn update_tiles( mut query: Query<(&TileState, &mut AseSlice, &Children)>, - mut crop_query: Query<&mut Visibility, With>, + mut crop_query: Query<&mut Visibility, (With, Without)>, + mut water_query: Query<&mut Visibility, (With, Without)>, asset_server: Res, ) { for (state, mut slice, children) in &mut query { @@ -102,6 +114,12 @@ fn update_tiles( _ => Visibility::Hidden, }; } + if let Ok(mut visibility) = water_query.get_mut(child) { + *visibility = match state { + TileState::Occupied { watered: true, .. } => Visibility::Visible, + _ => Visibility::Hidden, + }; + } } } } diff --git a/src/features/input/mod.rs b/src/features/input/mod.rs index ccb422b..d72d0b3 100644 --- a/src/features/input/mod.rs +++ b/src/features/input/mod.rs @@ -125,6 +125,7 @@ fn debug_click( seed: ItemType::BerrySeed { name: "Debug".into(), }, + watered: false, }, TileState::Occupied { .. } => TileState::Unclaimed, }, diff --git a/src/features/phase/mod.rs b/src/features/phase/mod.rs index 4181fb1..3c4a35d 100644 --- a/src/features/phase/mod.rs +++ b/src/features/phase/mod.rs @@ -157,8 +157,27 @@ fn handle_continue( mut phase_res: ResMut, mut session_tracker: ResMut, settings: Res, + mut tile_query: Query<&mut TileState>, ) { for _ in messages.read() { + let entering_break = if let Phase::Finished { completed_phase } = &phase_res.0 { + matches!(**completed_phase, Phase::Focus { .. }) + } else { + false + }; + next_phase(&mut phase_res, &mut session_tracker, &settings); + + if entering_break { + println!("Resetting watered state for all crops."); + for mut state in tile_query.iter_mut() { + if let TileState::Occupied { seed, .. } = &*state { + *state = TileState::Occupied { + seed: seed.clone(), + watered: false, + }; + } + } + } } } diff --git a/src/features/pom/actions.rs b/src/features/pom/actions.rs index afe0a26..6dd0b2b 100644 --- a/src/features/pom/actions.rs +++ b/src/features/pom/actions.rs @@ -3,12 +3,14 @@ use crate::prelude::*; #[derive(Clone, Debug, PartialEq)] pub enum InteractionAction { Plant(ItemType), + Water, } impl InteractionAction { pub fn get_name(&self, game_config: &GameConfig) -> String { match self { InteractionAction::Plant(item) => format!("Pflanze {}", item.singular(game_config)), + InteractionAction::Water => "Gießen".into(), } } @@ -19,6 +21,7 @@ impl InteractionAction { ) -> Option { match self { InteractionAction::Plant(item) => Some(item.get_sprite(asset_server, game_config)), + InteractionAction::Water => None, } } @@ -48,6 +51,15 @@ impl InteractionAction { } } + match tile_state { + TileState::Occupied { watered, .. } => { + if !*watered { + options.push(InteractionAction::Water); + } + } + _ => (), + } + options } @@ -81,6 +93,7 @@ impl InteractionAction { println!("Planting {:?}", seed_type); *tile_state = TileState::Occupied { seed: seed_type.clone(), + watered: false, }; } else { println!("No {:?} in inventory!", seed_type); @@ -89,6 +102,17 @@ impl InteractionAction { println!("Tile is not empty, cannot plant."); } } + InteractionAction::Water => { + if let TileState::Occupied { seed, .. } = &*tile_state { + println!("Watering {:?}", seed); + *tile_state = TileState::Occupied { + seed: seed.clone(), + watered: true, + }; + } else { + println!("Tile is not occupied, cannot water."); + } + } } } } diff --git a/tests/interaction.rs b/tests/interaction.rs index 4bd3014..3b69d35 100644 --- a/tests/interaction.rs +++ b/tests/interaction.rs @@ -91,7 +91,7 @@ fn test_plant_seed_interaction() { let tile_entity = grid.get_tile((1, 1)).unwrap(); let tile_state = app.world().entity(tile_entity).get::().unwrap(); - if let TileState::Occupied { seed } = tile_state { + if let TileState::Occupied { seed, .. } = tile_state { assert_eq!( *seed, ItemType::BerrySeed { @@ -164,3 +164,89 @@ fn test_plant_seed_no_inventory() { panic!("Tile should remain Empty, found {:?}", tile_state); } } + +#[test] +fn test_water_crop() { + let seed_type = ItemType::BerrySeed { + name: "TestSeed".into(), + }; + let mut app = setup_interaction_app( + 3, + 3, + &[( + 1, + 1, + TileState::Occupied { + seed: seed_type.clone(), + watered: false, + }, + )], + vec![], + ); + + // Verify Water option is available + let _ = app.world_mut().run_system_once( + move |grid: Res, + tile_query: Query<&TileState>, + inventory: Res, + item_query: Query<&ItemStack>| { + let tile_entity = grid.get_tile((1, 1)).unwrap(); + let tile_state = tile_query.get(tile_entity).unwrap(); + let options = InteractionAction::list_options(tile_state, &inventory, item_query); + + assert!( + options.contains(&InteractionAction::Water), + "Water option should be available" + ); + }, + ); + + // Execute Water + let _ = app.world_mut().run_system_once( + move |grid: Res, + mut tile_query: Query<&mut TileState>, + mut inventory: ResMut, + mut item_stack_query: Query<&mut ItemStack>, + mut commands: Commands| { + let action = InteractionAction::Water; + action.execute( + (1, 1), + &grid, + &mut tile_query, + &mut inventory, + &mut item_stack_query, + &mut commands, + ); + }, + ); + + app.update(); + + // Assert Tile State Watered + let grid = app.world().resource::(); + let tile_entity = grid.get_tile((1, 1)).unwrap(); + let tile_state = app.world().entity(tile_entity).get::().unwrap(); + + if let TileState::Occupied { watered, .. } = tile_state { + assert!(watered, "Tile should be watered"); + } else { + panic!("Tile should be Occupied, found {:?}", tile_state); + } + + // Verify Water option is NOT available + let _ = app.world_mut().run_system_once( + move |grid: Res, + tile_query: Query<&TileState>, + inventory: Res, + item_query: Query<&ItemStack>| { + let tile_entity = grid.get_tile((1, 1)).unwrap(); + let tile_state = tile_query.get(tile_entity).unwrap(); + let options = InteractionAction::list_options(tile_state, &inventory, item_query); + + assert!( + !options.contains(&InteractionAction::Water), + "Water option should NOT be available" + ); + }, + ); +} diff --git a/tests/pathfinding.rs b/tests/pathfinding.rs index 93bde1d..072b247 100644 --- a/tests/pathfinding.rs +++ b/tests/pathfinding.rs @@ -91,8 +91,9 @@ fn test_find_path_simple() { fn test_find_path_around_obstacle() { let obstacle: TileState = TileState::Occupied { seed: ItemType::BerrySeed { - name: "test".into(), + name: "Test".into(), }, + watered: false, }; let obstacles = vec![ (2, 2, obstacle.clone()), @@ -142,8 +143,9 @@ fn test_find_path_around_obstacle() { fn test_find_path_no_path() { let obstacle: TileState = TileState::Occupied { seed: ItemType::BerrySeed { - name: "test".into(), + name: "Test".into(), }, + watered: false, }; let obstacles = vec![ (2, 0, obstacle.clone()),