diff --git a/assets/crop.aseprite b/assets/crop.aseprite index 503cb41..08ecc00 100644 Binary files a/assets/crop.aseprite and b/assets/crop.aseprite differ diff --git a/src/features/grid/components.rs b/src/features/grid/components.rs index e2b20fa..571e63f 100644 --- a/src/features/grid/components.rs +++ b/src/features/grid/components.rs @@ -22,6 +22,10 @@ pub enum TileState { seed: ItemType, watered: bool, growth_stage: u32, + #[serde(default)] + withered: bool, + #[serde(default)] + dry_counter: u8, }, } diff --git a/src/features/grid/mod.rs b/src/features/grid/mod.rs index e94af2b..d2a064f 100644 --- a/src/features/grid/mod.rs +++ b/src/features/grid/mod.rs @@ -88,10 +88,10 @@ fn cleanup(mut commands: Commands, tile_query: Query>) { } fn update_tiles( - mut query: Query<(&TileState, &mut AseSlice, &Children)>, + mut query: Query<(&TileState, &mut AseSlice, &Children), (With, Without)>, mut crop_query: Query< - (&mut Visibility, &mut Transform), - (With, Without), + (&mut Visibility, &mut Transform, &mut AseSlice), + (With, Without, Without), >, mut water_query: Query< (&mut Visibility, &mut Transform), @@ -134,12 +134,18 @@ fn update_tiles( }; for child in children.iter() { - if let Ok((mut visibility, mut transform)) = crop_query.get_mut(child) { + if let Ok((mut visibility, mut transform, mut sprite)) = crop_query.get_mut(child) { *visibility = match state { TileState::Occupied { .. } => Visibility::Visible, _ => Visibility::Hidden, }; transform.scale = scale; + + if let TileState::Occupied { withered: true, .. } = state { + sprite.name = "Wither".into(); + } else { + sprite.name = "Crop".into(); + } } if let Ok((mut visibility, mut transform)) = water_query.get_mut(child) { *visibility = match state { diff --git a/src/features/input/mod.rs b/src/features/input/mod.rs index e002979..8ed6757 100644 --- a/src/features/input/mod.rs +++ b/src/features/input/mod.rs @@ -127,6 +127,8 @@ fn debug_click( }, watered: false, growth_stage: 0, + withered: false, + dry_counter: 0, }, TileState::Occupied { .. } => TileState::Unclaimed, }, diff --git a/src/features/phase/mod.rs b/src/features/phase/mod.rs index b4cb214..77cbf6d 100644 --- a/src/features/phase/mod.rs +++ b/src/features/phase/mod.rs @@ -176,21 +176,34 @@ pub fn handle_continue( seed, watered, growth_stage, + withered, + dry_counter, } = &*state { let mut new_stage = *growth_stage; + let mut new_withered = *withered; + let mut new_dry_counter = *dry_counter; + if *watered { + new_dry_counter = 0; if let Some(config) = seed.get_seed_config(&game_config) { - if new_stage < config.growth_stages { + if new_stage < config.growth_stages && !new_withered { new_stage += 1; } } + } else { + new_dry_counter += 1; + if new_dry_counter >= 2 { + new_withered = true; + } } *state = TileState::Occupied { seed: seed.clone(), watered: false, growth_stage: new_stage, + withered: new_withered, + dry_counter: new_dry_counter, }; } } diff --git a/src/features/pom/actions.rs b/src/features/pom/actions.rs index e63af7e..d88a62e 100644 --- a/src/features/pom/actions.rs +++ b/src/features/pom/actions.rs @@ -93,6 +93,8 @@ impl InteractionAction { seed: seed_type.clone(), watered: false, growth_stage: 0, + withered: false, + dry_counter: 0, }; } else { println!("No {:?} in inventory!", seed_type); @@ -102,12 +104,20 @@ impl InteractionAction { } } InteractionAction::Water => { - if let TileState::Occupied { seed, growth_stage, .. } = &*tile_state { + if let TileState::Occupied { + seed, + growth_stage, + withered, + .. + } = &*tile_state + { println!("Watering {:?}", seed); *tile_state = TileState::Occupied { seed: seed.clone(), watered: true, growth_stage: *growth_stage, + withered: *withered, + dry_counter: 0, }; } else { println!("Tile is not occupied, cannot water."); diff --git a/tests/growth.rs b/tests/growth.rs index 85ed8fb..00b6a9f 100644 --- a/tests/growth.rs +++ b/tests/growth.rs @@ -86,6 +86,8 @@ fn test_crop_growth_logic() { seed: seed_type.clone(), watered: true, growth_stage: 0, + withered: false, + dry_counter: 0, }, ), ( @@ -95,6 +97,8 @@ fn test_crop_growth_logic() { seed: seed_type.clone(), watered: false, growth_stage: 0, + withered: false, + dry_counter: 0, }, ), ( @@ -104,6 +108,8 @@ fn test_crop_growth_logic() { seed: seed_type.clone(), watered: true, growth_stage: 2, // Max + withered: false, + dry_counter: 0, }, ), ]; diff --git a/tests/interaction.rs b/tests/interaction.rs index b657ff2..7609fa8 100644 --- a/tests/interaction.rs +++ b/tests/interaction.rs @@ -180,6 +180,8 @@ fn test_water_crop() { seed: seed_type.clone(), watered: false, growth_stage: 0, + withered: false, + dry_counter: 0, }, )], vec![], diff --git a/tests/pathfinding.rs b/tests/pathfinding.rs index 99eba5a..4ddea09 100644 --- a/tests/pathfinding.rs +++ b/tests/pathfinding.rs @@ -95,6 +95,8 @@ fn test_find_path_around_obstacle() { }, watered: false, growth_stage: 0, + withered: false, + dry_counter: 0, }; let obstacles = vec![ (2, 2, obstacle.clone()), @@ -148,6 +150,8 @@ fn test_find_path_no_path() { }, watered: false, growth_stage: 0, + withered: false, + dry_counter: 0, }; let obstacles = vec![ (2, 0, obstacle.clone()), diff --git a/tests/withering.rs b/tests/withering.rs new file mode 100644 index 0000000..b6db5d1 --- /dev/null +++ b/tests/withering.rs @@ -0,0 +1,162 @@ +use bevy::ecs::system::RunSystemOnce; +use pomomon_garden::features::config::components::{BerrySeedConfig, GameConfig}; +use pomomon_garden::features::grid::components::{Grid, Tile, TileState}; +use pomomon_garden::features::inventory::components::ItemType; +use pomomon_garden::features::phase::components::{ + CurrentPhase, Phase, SessionTracker, TimerSettings, +}; +use pomomon_garden::features::phase::handle_continue; +use pomomon_garden::features::phase::messages::NextPhaseMessage; +use pomomon_garden::prelude::*; + +fn setup_withering_app( + grid_width: u32, + grid_height: u32, + initial_tile_states: &[(u32, u32, TileState)], +) -> App { + let mut app = App::new(); + app.add_plugins(MinimalPlugins); + app.add_plugins(AssetPlugin::default()); + + app.init_resource::(); + app.init_resource::(); + app.insert_resource(CurrentPhase(Phase::Finished { + completed_phase: Box::new(Phase::Focus { duration: 25.0 }), + })); + + app.insert_resource(GameConfig { + berry_seeds: vec![BerrySeedConfig { + name: "TestSeed".into(), + cost: 1, + grants: 1, + slice: "".into(), + growth_stages: 5, + }], + ..Default::default() + }); + + let mut grid_tiles = Vec::with_capacity(grid_width as usize); + for x in 0..grid_width { + let mut column = Vec::with_capacity(grid_height as usize); + for y in 0..grid_height { + let entity = app + .world_mut() + .spawn((Tile { x, y }, TileState::Unclaimed)) + .id(); + column.push(entity); + } + grid_tiles.push(column); + } + app.insert_resource(Grid { + width: grid_width, + height: grid_height, + tiles: grid_tiles, + }); + + for &(x, y, ref state) in initial_tile_states { + if let Ok(entity) = app.world().resource::().get_tile((x, y)) { + *app.world_mut().get_mut::(entity).unwrap() = state.clone(); + } + } + + app.add_message::(); + + app +} + +#[test] +fn test_withering_progression() { + let seed_type = ItemType::BerrySeed { + name: "TestSeed".into(), + }; + + // Start with a fresh plant + let initial_states = vec![( + 0, + 0, + TileState::Occupied { + seed: seed_type.clone(), + watered: false, + growth_stage: 0, + withered: false, + dry_counter: 0, + }, + )]; + + let mut app = setup_withering_app(1, 1, &initial_states); + + // --- Phase 1: Not watered --- + app.world_mut().write_message(NextPhaseMessage); + let _ = app.world_mut().run_system_once(handle_continue); + + let t = app.world().resource::().get_tile((0, 0)).unwrap(); + let s = app.world().entity(t).get::().unwrap(); + match s { + TileState::Occupied { + withered, + dry_counter, + .. + } => { + assert_eq!( + *dry_counter, 1, + "Dry counter should be 1 after 1st dry phase" + ); + assert_eq!(*withered, false, "Should not be withered yet"); + } + _ => panic!("Invalid state"), + } + + // --- Phase 2: Not watered -> Wither --- + // Reset phase to trigger update again + app.insert_resource(CurrentPhase(Phase::Finished { + completed_phase: Box::new(Phase::Focus { duration: 25.0 }), + })); + app.world_mut().write_message(NextPhaseMessage); + let _ = app.world_mut().run_system_once(handle_continue); + + let s = app.world().entity(t).get::().unwrap(); + match s { + TileState::Occupied { + withered, + dry_counter, + .. + } => { + assert_eq!(*dry_counter, 2, "Dry counter should be 2"); + assert_eq!(*withered, true, "Should be withered now"); + } + _ => panic!("Invalid state"), + } + + // --- Phase 3: Watered -> Withered persists --- + // Manually water it (simulate user action) + if let Some(mut s_mut) = app.world_mut().get_mut::(t) { + match *s_mut { + TileState::Occupied { + ref mut watered, .. + } => *watered = true, + _ => (), + } + } + + // Run phase + app.insert_resource(CurrentPhase(Phase::Finished { + completed_phase: Box::new(Phase::Focus { duration: 25.0 }), + })); + app.world_mut().write_message(NextPhaseMessage); + let _ = app.world_mut().run_system_once(handle_continue); + + let s = app.world().entity(t).get::().unwrap(); + match s { + TileState::Occupied { + growth_stage, + withered, + dry_counter, + .. + } => { + assert_eq!(*dry_counter, 0, "Watering should reset dry counter"); + assert_eq!(*withered, true, "Should still be withered"); + assert_eq!(*growth_stage, 0, "Should not grow if withered"); + } + _ => panic!("Invalid state"), + } +}