Merge branch '28-crop-withering' into 'dev'
Implement crop withering See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!26
This commit is contained in:
Binary file not shown.
@@ -22,6 +22,10 @@ pub enum TileState {
|
|||||||
seed: ItemType,
|
seed: ItemType,
|
||||||
watered: bool,
|
watered: bool,
|
||||||
growth_stage: u32,
|
growth_stage: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
withered: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
dry_counter: u8,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,10 +88,10 @@ fn cleanup(mut commands: Commands, tile_query: Query<Entity, With<Tile>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_tiles(
|
fn update_tiles(
|
||||||
mut query: Query<(&TileState, &mut AseSlice, &Children)>,
|
mut query: Query<(&TileState, &mut AseSlice, &Children), (With<Tile>, Without<CropVisual>)>,
|
||||||
mut crop_query: Query<
|
mut crop_query: Query<
|
||||||
(&mut Visibility, &mut Transform),
|
(&mut Visibility, &mut Transform, &mut AseSlice),
|
||||||
(With<CropVisual>, Without<WaterVisual>),
|
(With<CropVisual>, Without<WaterVisual>, Without<Tile>),
|
||||||
>,
|
>,
|
||||||
mut water_query: Query<
|
mut water_query: Query<
|
||||||
(&mut Visibility, &mut Transform),
|
(&mut Visibility, &mut Transform),
|
||||||
@@ -134,12 +134,18 @@ fn update_tiles(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for child in children.iter() {
|
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 {
|
*visibility = match state {
|
||||||
TileState::Occupied { .. } => Visibility::Visible,
|
TileState::Occupied { .. } => Visibility::Visible,
|
||||||
_ => Visibility::Hidden,
|
_ => Visibility::Hidden,
|
||||||
};
|
};
|
||||||
transform.scale = scale;
|
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) {
|
if let Ok((mut visibility, mut transform)) = water_query.get_mut(child) {
|
||||||
*visibility = match state {
|
*visibility = match state {
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ fn debug_click(
|
|||||||
},
|
},
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
},
|
},
|
||||||
TileState::Occupied { .. } => TileState::Unclaimed,
|
TileState::Occupied { .. } => TileState::Unclaimed,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -176,21 +176,34 @@ pub fn handle_continue(
|
|||||||
seed,
|
seed,
|
||||||
watered,
|
watered,
|
||||||
growth_stage,
|
growth_stage,
|
||||||
|
withered,
|
||||||
|
dry_counter,
|
||||||
} = &*state
|
} = &*state
|
||||||
{
|
{
|
||||||
let mut new_stage = *growth_stage;
|
let mut new_stage = *growth_stage;
|
||||||
|
let mut new_withered = *withered;
|
||||||
|
let mut new_dry_counter = *dry_counter;
|
||||||
|
|
||||||
if *watered {
|
if *watered {
|
||||||
|
new_dry_counter = 0;
|
||||||
if let Some(config) = seed.get_seed_config(&game_config) {
|
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;
|
new_stage += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
new_dry_counter += 1;
|
||||||
|
if new_dry_counter >= 2 {
|
||||||
|
new_withered = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*state = TileState::Occupied {
|
*state = TileState::Occupied {
|
||||||
seed: seed.clone(),
|
seed: seed.clone(),
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: new_stage,
|
growth_stage: new_stage,
|
||||||
|
withered: new_withered,
|
||||||
|
dry_counter: new_dry_counter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ impl InteractionAction {
|
|||||||
seed: seed_type.clone(),
|
seed: seed_type.clone(),
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
println!("No {:?} in inventory!", seed_type);
|
println!("No {:?} in inventory!", seed_type);
|
||||||
@@ -102,12 +104,20 @@ impl InteractionAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
InteractionAction::Water => {
|
InteractionAction::Water => {
|
||||||
if let TileState::Occupied { seed, growth_stage, .. } = &*tile_state {
|
if let TileState::Occupied {
|
||||||
|
seed,
|
||||||
|
growth_stage,
|
||||||
|
withered,
|
||||||
|
..
|
||||||
|
} = &*tile_state
|
||||||
|
{
|
||||||
println!("Watering {:?}", seed);
|
println!("Watering {:?}", seed);
|
||||||
*tile_state = TileState::Occupied {
|
*tile_state = TileState::Occupied {
|
||||||
seed: seed.clone(),
|
seed: seed.clone(),
|
||||||
watered: true,
|
watered: true,
|
||||||
growth_stage: *growth_stage,
|
growth_stage: *growth_stage,
|
||||||
|
withered: *withered,
|
||||||
|
dry_counter: 0,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
println!("Tile is not occupied, cannot water.");
|
println!("Tile is not occupied, cannot water.");
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ fn test_crop_growth_logic() {
|
|||||||
seed: seed_type.clone(),
|
seed: seed_type.clone(),
|
||||||
watered: true,
|
watered: true,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -95,6 +97,8 @@ fn test_crop_growth_logic() {
|
|||||||
seed: seed_type.clone(),
|
seed: seed_type.clone(),
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -104,6 +108,8 @@ fn test_crop_growth_logic() {
|
|||||||
seed: seed_type.clone(),
|
seed: seed_type.clone(),
|
||||||
watered: true,
|
watered: true,
|
||||||
growth_stage: 2, // Max
|
growth_stage: 2, // Max
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ fn test_water_crop() {
|
|||||||
seed: seed_type.clone(),
|
seed: seed_type.clone(),
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
},
|
},
|
||||||
)],
|
)],
|
||||||
vec![],
|
vec![],
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ fn test_find_path_around_obstacle() {
|
|||||||
},
|
},
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
};
|
};
|
||||||
let obstacles = vec![
|
let obstacles = vec![
|
||||||
(2, 2, obstacle.clone()),
|
(2, 2, obstacle.clone()),
|
||||||
@@ -148,6 +150,8 @@ fn test_find_path_no_path() {
|
|||||||
},
|
},
|
||||||
watered: false,
|
watered: false,
|
||||||
growth_stage: 0,
|
growth_stage: 0,
|
||||||
|
withered: false,
|
||||||
|
dry_counter: 0,
|
||||||
};
|
};
|
||||||
let obstacles = vec![
|
let obstacles = vec![
|
||||||
(2, 0, obstacle.clone()),
|
(2, 0, obstacle.clone()),
|
||||||
|
|||||||
162
tests/withering.rs
Normal file
162
tests/withering.rs
Normal file
@@ -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::<TimerSettings>();
|
||||||
|
app.init_resource::<SessionTracker>();
|
||||||
|
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::<Grid>().get_tile((x, y)) {
|
||||||
|
*app.world_mut().get_mut::<TileState>(entity).unwrap() = state.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.add_message::<NextPhaseMessage>();
|
||||||
|
|
||||||
|
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::<Grid>().get_tile((0, 0)).unwrap();
|
||||||
|
let s = app.world().entity(t).get::<TileState>().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::<TileState>().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::<TileState>(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::<TileState>().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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user