use bevy::prelude::*; use crate::{ sim::{SimSystems, SimTick}, tilemap::PlantLook, world::{Terrain, TileChanged, WorldMap}, }; pub struct PlantsPlugin; impl Plugin for PlantsPlugin { fn build(&self, app: &mut App) { app.add_systems(FixedUpdate, grow_plants.in_set(SimSystems::Act)); } } pub const GROWTH_INTERVAL_TICKS: u64 = 60; const WILD_MIN_FERTILITY: f32 = 0.8; pub fn fertility(terrain: Terrain) -> f32 { match terrain { Terrain::Soil => 1.0, Terrain::Grass => 0.8, Terrain::Sand => 0.4, Terrain::Gravel => 0.3, _ => 0.0, } } pub fn grow(map: &mut WorldMap, elapsed_ticks: f32, mut on_change: impl FnMut(UVec2)) { for pos in map.positions() { let p = pos.as_ivec2(); let Some(plant) = map.plant(p) else { continue; }; let Some(ripening) = plant.ripening_ticks() else { continue; }; let growth = map.growth(p); if growth >= 1.0 { continue; } let soil = fertility(map.get(p).unwrap_or(Terrain::Grass)); let soil = match plant { crate::world::Plant::Crop => soil, _ => soil.max(WILD_MIN_FERTILITY), }; let grown = (growth + elapsed_ticks / ripening * soil).min(1.0); map.set_growth(pos, grown); if PlantLook::of(plant, growth) != PlantLook::of(plant, grown) { on_change(pos); } } } fn grow_plants( tick: Res, mut map: ResMut, mut changed: MessageWriter, ) { if !tick.0.is_multiple_of(GROWTH_INTERVAL_TICKS) { return; } grow(&mut map, GROWTH_INTERVAL_TICKS as f32, |pos| { changed.write(TileChanged(pos)); }); } #[cfg(test)] mod tests { use super::*; use crate::world::Plant; fn growth_after(terrain: Terrain, plant: Plant, ticks: f32) -> f32 { let mut map = WorldMap::filled(UVec2::splat(2), terrain); map.set_plant(UVec2::ZERO, Some(plant)); grow(&mut map, ticks, |_| {}); map.growth(IVec2::ZERO) } #[test] fn crops_ripen_faster_on_fertile_soil() { let day = 24.0 * crate::sim::TICKS_PER_HOUR as f32; assert!( growth_after(Terrain::Soil, Plant::Crop, day) > growth_after(Terrain::Sand, Plant::Crop, day) ); assert_eq!(growth_after(Terrain::Soil, Plant::Crop, 2.0 * day), 1.0); } #[test] fn trees_do_not_grow_and_stage_changes_are_reported() { assert_eq!(growth_after(Terrain::Soil, Plant::Tree, 1e9), 0.0); let mut map = WorldMap::filled(UVec2::splat(2), Terrain::Soil); map.set_plant(UVec2::ZERO, Some(Plant::Crop)); let mut changes = 0; grow(&mut map, 1e9, |_| changes += 1); assert_eq!(changes, 1); } }