diff --git a/common/src/spiral.rs b/common/src/spiral.rs index 2a76a3484d..93c59fef11 100644 --- a/common/src/spiral.rs +++ b/common/src/spiral.rs @@ -11,6 +11,11 @@ pub struct Spiral2d { impl Spiral2d { #[allow(clippy::new_without_default)] // TODO: Pending review in #587 pub fn new() -> Self { Self { layer: 0, i: 0 } } + + pub fn radius(self, radius: i32) -> impl Iterator> { + self.take((radius * 2 + 1).pow(2) as usize) + .filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2)) + } } impl Iterator for Spiral2d { diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index e0df5e832b..129b37545c 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,7 +1,9 @@ use crate::{ all::ForestKind, block::StructureMeta, - sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim, Path}, + sim::{ + local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, Path, RiverKind, SimChunk, WorldSim, + }, util::Sampler, Index, CONFIG, }; @@ -196,6 +198,7 @@ where let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?; + let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?; let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?; let sim_chunk = sim.get(chunk_pos)?; let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64); @@ -860,8 +863,8 @@ where let cold_stone = Rgb::new(0.57, 0.67, 0.8); let hot_stone = Rgb::new(0.07, 0.07, 0.06); let warm_stone = Rgb::new(0.77, 0.77, 0.64); - let beach_sand = Rgb::new(0.9, 0.82, 0.6); - let desert_sand = Rgb::new(0.95, 0.75, 0.5); + let beach_sand = Rgb::new(0.8, 0.75, 0.5); + let desert_sand = Rgb::new(0.7, 0.4, 0.25); let snow = Rgb::new(0.8, 0.85, 1.0); let stone_col = Rgb::new(195, 187, 201); @@ -1088,11 +1091,15 @@ where water_level, warp_factor, surface_color: Rgb::lerp( - Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)), - // Land - ground, - // Beach - ((ocean_level - 1.0) / 2.0).max(0.0), + sub_surface_color, + Rgb::lerp( + Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)), + // Land + ground, + // Beach + ((ocean_level - 1.0) / 2.0).max(0.0), + ), + surface_veg, ), sub_surface_color, // No growing directly on bedrock. diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index d8f6b9228d..7cb81635e6 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -37,7 +37,9 @@ pub fn apply_paths_to<'a>( }) }; - if let Some((path_dist, path_nearest, path, _)) = col_sample.path.filter(|(dist, _, path, _)| *dist < path.width) + if let Some((path_dist, path_nearest, path, _)) = col_sample + .path + .filter(|(dist, _, path, _)| *dist < path.width) { let inset = 0; @@ -75,9 +77,9 @@ pub fn apply_paths_to<'a>( if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) } else { - let path_color = col_sample - .sub_surface_color - .map(|e| (e * 255.0 * 0.7) as u8); + let path_color = path.surface_color( + col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), + ); Block::new(BlockKind::Normal, noisy_color(path_color, 8)) }, ); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 00c95058e3..3c0ba76b9d 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1699,8 +1699,8 @@ impl WorldSim { Some(z0 + z1 + z2 + z3) } - /// Return the distance to the nearest path in blocks, along with the closest point on the path - /// and the tangent vector of that path. + /// Return the distance to the nearest path in blocks, along with the closest point on the + /// path, the path metadata, and the tangent vector of that path. pub fn get_nearest_path(&self, wpos: Vec2) -> Option<(f32, Vec2, Path, Vec2)> { let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { e.div_euclid(sz as i32) @@ -1761,7 +1761,9 @@ impl WorldSim { .clamped(0.0, 1.0); let pos = bez.evaluate(nearest_interval); let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32)); - Some((dist_sqrd, pos, chunk.path.path, move || bez.evaluate_derivative(nearest_interval).normalized())) + Some((dist_sqrd, pos, chunk.path.path, move || { + bez.evaluate_derivative(nearest_interval).normalized() + })) }), ) }) @@ -1789,6 +1791,7 @@ pub struct SimChunk { pub spawn_rate: f32, pub river: RiverData, pub warp_factor: f32, + pub surface_veg: f32, pub sites: Vec>, pub place: Option>, @@ -2024,6 +2027,7 @@ impl SimChunk { spawn_rate: 1.0, river, warp_factor: 1.0, + surface_veg: 1.0, sites: Vec::new(), place: None, diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs index c8ac115b38..3756b14dc6 100644 --- a/world/src/sim/path.rs +++ b/world/src/sim/path.rs @@ -2,7 +2,7 @@ use vek::*; #[derive(Copy, Clone, Debug)] pub struct Path { - pub width: f32, + pub width: f32, // Actually radius } #[derive(Debug)] @@ -15,23 +15,27 @@ pub struct PathData { impl PathData { pub fn is_path(&self) -> bool { self.neighbors != 0 } + + pub fn clear(&mut self) { self.neighbors = 0; } } impl Default for PathData { fn default() -> Self { Self { offset: Vec2::zero(), - path: Path { - width: 5.0, - }, + path: Path { width: 5.0 }, neighbors: 0, } } } impl Path { - /// Return the number of blocks of headspace required at the given path distance + /// Return the number of blocks of headspace required at the given path + /// distance pub fn head_space(&self, dist: f32) -> i32 { (8 - (dist * 0.25).powf(6.0).round() as i32).max(1) } + + /// Get the surface colour of a path given the surrounding surface color + pub fn surface_color(&self, col: Rgb) -> Rgb { col.map(|e| (e as f32 * 0.7) as u8) } } diff --git a/world/src/site/castle/mod.rs b/world/src/site/castle/mod.rs index 12952c6d52..9858a6f568 100644 --- a/world/src/site/castle/mod.rs +++ b/world/src/site/castle/mod.rs @@ -5,7 +5,7 @@ use crate::{ sim::WorldSim, site::{ settlement::building::{ - archetype::keep::{Attr, Keep}, + archetype::keep::{Attr, Keep as KeepArchetype}, Archetype, Branch, Ori, }, BlockMask, @@ -18,6 +18,7 @@ use common::{ comp, generation::{ChunkSupplement, EntityInfo}, npc, + spiral::Spiral2d, store::{Id, Store}, terrain::{Block, BlockKind, Structure, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, @@ -29,11 +30,12 @@ use rand::prelude::*; use std::sync::Arc; use vek::*; -struct Segment { +struct Keep { offset: Vec2, locus: i32, height: i32, is_tower: bool, + alt: i32, } struct Tower { @@ -47,29 +49,44 @@ pub struct Castle { seed: u32, radius: i32, towers: Vec, - segments: Vec, + keeps: Vec, rounded_towers: bool, } pub struct GenCtx<'a, R: Rng> { - sim: Option<&'a WorldSim>, + sim: Option<&'a mut WorldSim>, rng: &'a mut R, } impl Castle { #[allow(clippy::let_and_return)] // TODO: Pending review in #587 - pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { + pub fn generate(wpos: Vec2, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self { let mut ctx = GenCtx { sim, rng }; let boundary_towers = ctx.rng.gen_range(5, 10); + let keep_count = ctx.rng.gen_range(1, 4); let boundary_noise = ctx.rng.gen_range(-2i32, 8).max(1) as f32; let radius = 150; + // Adjust ground + if let Some(sim) = ctx.sim.as_mut() { + for rpos in Spiral2d::new() + .radius((radius as f32 * 0.7) as i32 / TerrainChunkSize::RECT_SIZE.x as i32) + { + sim.get_mut(wpos / TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + rpos) + .map(|chunk| { + chunk.surface_veg = 0.0; + chunk.path.clear(); + }); + } + } + let this = Self { origin: wpos, alt: ctx .sim + .as_ref() .and_then(|sim| sim.get_alt_approx(wpos)) .unwrap_or(0.0) as i32 + 6, @@ -87,6 +104,7 @@ impl Castle { for i in (1..80).step_by(5) { if ctx .sim + .as_ref() .and_then(|sim| sim.get_nearest_path(wpos + offset)) .map(|(dist, _, _, _)| dist > 24.0) .unwrap_or(true) @@ -101,6 +119,7 @@ impl Castle { offset, alt: ctx .sim + .as_ref() .and_then(|sim| sim.get_alt_approx(wpos + offset)) .unwrap_or(0.0) as i32 + 2, @@ -108,19 +127,28 @@ impl Castle { }) .collect(), rounded_towers: ctx.rng.gen(), + keeps: (0..keep_count) + .map(|i| { + let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0; + let dir = Vec2::new(angle.cos(), angle.sin()); + let dist = + (radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3; - segments: (0..0) //rng.gen_range(18, 24)) - .map(|_| { - let dir = Vec2::new(ctx.rng.gen_range(-1.0, 1.0), ctx.rng.gen_range(-1.0, 1.0)) - .normalized(); - let dist = 16.0 + ctx.rng.gen_range(0.0f32, 1.0).powf(0.5) * 64.0; - let height = 48.0 - (dist / 64.0).powf(2.0) * 32.0; + let locus = ctx.rng.gen_range(20, 26); + let offset = (dir * dist).map(|e| e as i32); + let height = ctx.rng.gen_range(25, 70).clamped(30, 48); - Segment { - offset: (dir * dist).map(|e| e as i32), - locus: ctx.rng.gen_range(6, 26), - height: height as i32, - is_tower: height > 36.0, + Keep { + offset, + locus, + height, + is_tower: true, + alt: ctx + .sim + .as_ref() + .and_then(|sim| sim.get_alt_approx(wpos + offset)) + .unwrap_or(0.0) as i32 + + 2, } }) .collect(), @@ -209,7 +237,8 @@ impl Castle { } else { rpos.yx() }; - let head_space = col_sample.path + let head_space = col_sample + .path .map(|(dist, _, path, _)| path.head_space(dist)) .unwrap_or(0); @@ -220,34 +249,37 @@ impl Castle { col_sample }; + let keep_archetype = KeepArchetype { + flag_color: Rgb::new(200, 80, 40), + }; + for z in -10..64 { let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z); - let keep = Keep { - flag_color: Rgb::new(200, 80, 40), - }; - - // Boundary + // Boundary wall let wall_z = wpos.z - wall_alt; - let mut mask = if z < head_space { - BlockMask::nothing() - } else { - keep.draw( - Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt, - wall_dist, - Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()), - rpos - wall_pos, - wall_z, - wall_ori, - 4, - 0, - &Attr { - height: 16, - is_tower: false, - rounded: true, - }, - ) - }; + if z < head_space { + continue; + } + + let mut mask = keep_archetype.draw( + Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt, + wall_dist, + Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()), + rpos - wall_pos, + wall_z, + wall_ori, + 4, + 0, + &Attr { + height: 16, + is_tower: false, + ridged: false, + rounded: true, + }, + ); + + // Boundary towers for tower in &self.towers { let tower_wpos = Vec3::new( self.origin.x + tower.offset.x, @@ -257,7 +289,7 @@ impl Castle { let tower_locus = 10; let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs()); - mask = mask.resolve_with(keep.draw( + mask = mask.resolve_with(keep_archetype.draw( if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() { wpos - tower_wpos } else { @@ -277,6 +309,42 @@ impl Castle { &Attr { height: 28, is_tower: true, + ridged: false, + rounded: self.rounded_towers, + }, + )); + } + + // Keeps + for keep in &self.keeps { + let keep_wpos = Vec3::new( + self.origin.x + keep.offset.x, + self.origin.y + keep.offset.y, + keep.alt, + ); + + let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs()); + mask = mask.resolve_with(keep_archetype.draw( + if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() { + wpos - keep_wpos + } else { + Vec3::new( + wpos.y - keep_wpos.y, + wpos.x - keep_wpos.x, + wpos.z - keep_wpos.z, + ) + }, + border_pos.reduce_max() - keep.locus, + Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()), + (wpos - keep_wpos).xy(), + wpos.z - keep.alt, + Ori::East, + keep.locus, + 0, + &Attr { + height: keep.height, + is_tower: keep.is_tower, + ridged: true, rounded: self.rounded_towers, }, )); diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 4c9a2492ef..ca6c190773 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -105,7 +105,7 @@ impl Attr { }, mansard: rng.gen_range(-7, 4).max(0), pillar: match rng.gen_range(0, 4) { - 0 => Pillar::Chimney(rng.gen_range(1, 5)), + 0 => Pillar::Chimney(rng.gen_range(2, 6)), _ => Pillar::None, }, levels: rng.gen_range(1, 3), @@ -135,7 +135,7 @@ impl Archetype for House { storey_fill: StoreyFill::All, mansard: 0, pillar: match rng.gen_range(0, 3) { - 0 => Pillar::Chimney(rng.gen_range(1, 5)), + 0 => Pillar::Chimney(rng.gen_range(2, 6)), 1 => Pillar::Tower(5 + rng.gen_range(1, 5)), _ => Pillar::None, }, @@ -409,7 +409,7 @@ impl Archetype for House { // Window if (frame_bounds.size() + 1).reduce_min() > 2 { // Window frame is large enough for a window - let surface_pos = Vec2::new(bound_offset.x, profile.y - floor_height); + let surface_pos = Vec2::new(bound_offset.x, profile.y); if window_bounds.contains_point(surface_pos) { return Some(end_window); } else if frame_bounds.contains_point(surface_pos) { diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 51a5c47c15..3f234dc737 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -14,6 +14,7 @@ pub struct Keep { pub struct Attr { pub height: i32, pub is_tower: bool, + pub ridged: bool, pub rounded: bool, } @@ -30,6 +31,7 @@ impl Archetype for Keep { attr: Attr { height: rng.gen_range(12, 16), is_tower: false, + ridged: false, rounded: true, }, locus: 10 + rng.gen_range(0, 5), @@ -43,6 +45,7 @@ impl Archetype for Keep { attr: Attr { height: rng.gen_range(20, 28), is_tower: true, + ridged: false, rounded: true, }, locus: 4 + rng.gen_range(0, 5), @@ -104,15 +107,21 @@ impl Archetype for Keep { let internal = BlockMask::new(Block::empty(), internal_layer); let empty = BlockMask::nothing(); - let width = locus; - let rampart_width = 2 + locus; - let ceil_height = attr.height; - let door_height = 6; - let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) { + let edge_pos = if (bound_offset.x.abs() > bound_offset.y.abs()) ^ (ori == Ori::East) { pos.y } else { pos.x }; + + let width = locus + + if edge_pos % 4 == 0 && attr.ridged && !attr.rounded { + 1 + } else { + 0 + }; + let rampart_width = 2 + width; + let ceil_height = attr.height; + let door_height = 6; let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 }; let min_dist = if attr.rounded { bound_offset.map(|e| e.pow(2) as f32).sum().powf(0.5) as i32 diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 4bba7a17f7..ace88fcf90 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -593,14 +593,15 @@ impl Settlement { // if let Some((WayKind::Path, dist, nearest)) = sample.way { // let inset = -1; - // // Try to use the column at the centre of the path for sampling to make them - // // flatter + // // Try to use the column at the centre of the path for sampling to make + // them // flatter // let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos)) // .unwrap_or(col_sample); // let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist { // ( - // ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0, - // ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) + // ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * + // 5.0, ((1.0 - ((water_dist + 2.0) * + // 0.3).min(0.0).cos().abs()) // * (col.riverless_alt + 5.0 - col.alt).max(0.0) // * 1.75 // + 3.0) as i32, @@ -614,10 +615,10 @@ impl Settlement { // let _ = vol.set( // Vec3::new(offs.x, offs.y, surface_z + z), // if bridge_offset >= 2.0 && dist >= 3.0 || z < inset - 1 { - // Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) - // } else { - // Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, 30), 8)) - // }, + // Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, + // 100), 8)) } else { + // Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, + // 30), 8)) }, // ); // } // let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1); @@ -639,6 +640,7 @@ impl Settlement { Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)), Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), + Some(Plot::Town { district }) => None, Some(Plot::Town { district }) => { if let Some((_, path_nearest, _, _)) = col_sample.path { let path_dir = (path_nearest - wpos2d.map(|e| e as f32)) @@ -669,65 +671,87 @@ impl Settlement { })) }, Some(Plot::Field { seed, crop, .. }) => { - let furrow_dirs = [ - Vec2::new(1, 0), - Vec2::new(0, 1), - Vec2::new(1, 1), - Vec2::new(-1, 1), - ]; - let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; - let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; + if let Some(color) = col_sample.path.and_then(|(dist, _, path, _)| { + if dist < path.width { + Some(path.surface_color( + col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), + )) + } else { + None + } + }) { + Some(color) + } else { + let furrow_dirs = [ + Vec2::new(1, 0), + Vec2::new(0, 1), + Vec2::new(1, 1), + Vec2::new(-1, 1), + ]; + let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; + let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; - let dirt = Rgb::new(80, 55, 35).map(|e| { - e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) - as u8 - }); - let mound = - Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| { - e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) + let dirt = Rgb::new(80, 55, 35).map(|e| { + e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) as u8 }); + let mound = + Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| { + e + (self + .noise + .get(Vec3::broadcast((seed % 4096 + 1) as i32)) + % 32) as u8 + }); - if in_furrow { - if roll(0, 5) == 0 { - surface_block = match crop { - Crop::Corn => Some(BlockKind::Corn), - Crop::Wheat if roll(1, 2) == 0 => { - Some(BlockKind::WheatYellow) - }, - Crop::Wheat => Some(BlockKind::WheatGreen), - Crop::Cabbage if roll(2, 2) == 0 => { - Some(BlockKind::Cabbage) - }, - Crop::Pumpkin if roll(3, 2) == 0 => { - Some(BlockKind::Pumpkin) - }, - Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax), - Crop::Carrot if roll(5, 2) == 0 => Some(BlockKind::Carrot), - Crop::Tomato if roll(6, 2) == 0 => Some(BlockKind::Tomato), - Crop::Radish if roll(7, 2) == 0 => Some(BlockKind::Radish), - Crop::Turnip if roll(8, 2) == 0 => Some(BlockKind::Turnip), - Crop::Sunflower => Some(BlockKind::Sunflower), - _ => None, - } - .or_else(|| { - if roll(9, 400) == 0 { - Some(BlockKind::Scarecrow) - } else { - None + if in_furrow { + if roll(0, 5) == 0 { + surface_block = match crop { + Crop::Corn => Some(BlockKind::Corn), + Crop::Wheat if roll(1, 2) == 0 => { + Some(BlockKind::WheatYellow) + }, + Crop::Wheat => Some(BlockKind::WheatGreen), + Crop::Cabbage if roll(2, 2) == 0 => { + Some(BlockKind::Cabbage) + }, + Crop::Pumpkin if roll(3, 2) == 0 => { + Some(BlockKind::Pumpkin) + }, + Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax), + Crop::Carrot if roll(5, 2) == 0 => { + Some(BlockKind::Carrot) + }, + Crop::Tomato if roll(6, 2) == 0 => { + Some(BlockKind::Tomato) + }, + Crop::Radish if roll(7, 2) == 0 => { + Some(BlockKind::Radish) + }, + Crop::Turnip if roll(8, 2) == 0 => { + Some(BlockKind::Turnip) + }, + Crop::Sunflower => Some(BlockKind::Sunflower), + _ => None, } - }) - .map(|kind| Block::new(kind, Rgb::white())); + .or_else(|| { + if roll(9, 400) == 0 { + Some(BlockKind::Scarecrow) + } else { + None + } + }) + .map(|kind| Block::new(kind, Rgb::white())); + } + } else if roll(0, 20) == 0 { + surface_block = + Some(Block::new(BlockKind::ShortGrass, Rgb::white())); + } else if roll(1, 30) == 0 { + surface_block = + Some(Block::new(BlockKind::MediumGrass, Rgb::white())); } - } else if roll(0, 20) == 0 { - surface_block = - Some(Block::new(BlockKind::ShortGrass, Rgb::white())); - } else if roll(1, 30) == 0 { - surface_block = - Some(Block::new(BlockKind::MediumGrass, Rgb::white())); - } - Some(if in_furrow { dirt } else { mound }) + Some(if in_furrow { dirt } else { mound }) + } }, _ => None, };