diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index bd6cc5ece6..92559d6ab1 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -113,7 +113,15 @@ impl Data { let matches_buildings = (|kind: &PlotKind| { matches!( kind, - PlotKind::House(_) | PlotKind::Workshop(_) | PlotKind::Plaza + PlotKind::House(_) + | PlotKind::Workshop(_) + | PlotKind::Plaza + | PlotKind::SavannahPit(_) + | PlotKind::SavannahHut(_) + | PlotKind::SavannahWorkshop(_) + | PlotKind::CliffTower(_) + | PlotKind::DesertCityMultiPlot(_) + | PlotKind::DesertCityTemple(_) ) }) as _; let matches_plazas = (|kind: &PlotKind| matches!(kind, PlotKind::Plaza)) as _; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index c079a4ceb5..cd0735c696 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -362,7 +362,7 @@ impl Civs { SiteKind::Castle => (16i32, 5.0), SiteKind::Refactor => (32i32, 10.0), SiteKind::CliffTown => (32i32, 10.0), - SiteKind::SavannahPit => (36i32, 20.0), + SiteKind::SavannahPit => (48i32, 25.0), SiteKind::DesertCity => (64i32, 25.0), SiteKind::ChapelSite => (36i32, 10.0), SiteKind::Tree => (12i32, 8.0), diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 72e6049ccf..95c3ed0ed4 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -893,6 +893,11 @@ impl Site { name: NameGen::location(&mut rng).generate_savannah_custom(), ..Site::default() }; + + site.demarcate_obstacles(land); + + site.make_plaza(land, &mut rng); + let size = 11.0 as i32; let aabr = Aabr { min: Vec2::broadcast(-size), @@ -915,6 +920,86 @@ impl Site { hard_alt: Some(savannah_pit_alt), }); } + + let build_chance = Lottery::from(vec![(38.0, 1), (7.0, 2)]); + + for _ in 0..45 { + match *build_chance.choose_seeded(rng.gen()) { + 1 => { + // SavannahHut + + let size = (4.0 + rng.gen::().powf(5.0) * 1.5).round() as u32; + if let Some((aabr, door_tile, door_dir)) = attempt(32, || { + site.find_roadside_aabr( + &mut rng, + 4..(size + 1).pow(2), + Extent2::broadcast(size), + ) + }) { + let savannah_hut = plot::SavannahHut::generate( + land, + &mut reseed(&mut rng), + &site, + door_tile, + door_dir, + aabr, + ); + let savannah_hut_alt = savannah_hut.alt; + let plot = site.create_plot(Plot { + kind: PlotKind::SavannahHut(savannah_hut), + root_tile: aabr.center(), + tiles: aabr_tiles(aabr).collect(), + seed: rng.gen(), + }); + + site.blit_aabr(aabr, Tile { + kind: TileKind::Building, + plot: Some(plot), + hard_alt: Some(savannah_hut_alt), + }); + } else { + site.make_plaza(land, &mut rng); + } + }, + 2 => { + // SavannahWorkshop + + let size = (4.0 + rng.gen::().powf(5.0) * 1.5).round() as u32; + if let Some((aabr, door_tile, door_dir)) = attempt(32, || { + site.find_roadside_aabr( + &mut rng, + 4..(size + 1).pow(2), + Extent2::broadcast(size), + ) + }) { + let savannah_workshop = plot::SavannahWorkshop::generate( + land, + &mut reseed(&mut rng), + &site, + door_tile, + door_dir, + aabr, + ); + let savannah_workshop_alt = savannah_workshop.alt; + let plot = site.create_plot(Plot { + kind: PlotKind::SavannahWorkshop(savannah_workshop), + root_tile: aabr.center(), + tiles: aabr_tiles(aabr).collect(), + seed: rng.gen(), + }); + + site.blit_aabr(aabr, Tile { + kind: TileKind::Building, + plot: Some(plot), + hard_alt: Some(savannah_workshop_alt), + }); + } else { + site.make_plaza(land, &mut rng); + } + }, + _ => {}, + } + } site } @@ -1382,6 +1467,10 @@ impl Site { PlotKind::GiantTree(giant_tree) => giant_tree.render_collect(self, canvas), PlotKind::CliffTower(cliff_tower) => cliff_tower.render_collect(self, canvas), PlotKind::SavannahPit(savannah_pit) => savannah_pit.render_collect(self, canvas), + PlotKind::SavannahHut(savannah_hut) => savannah_hut.render_collect(self, canvas), + PlotKind::SavannahWorkshop(savannah_workshop) => { + savannah_workshop.render_collect(self, canvas) + }, PlotKind::DesertCityMultiPlot(desert_city_multi_plot) => { desert_city_multi_plot.render_collect(self, canvas) }, diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index e347e191fa..3d17ba4708 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -9,7 +9,9 @@ pub mod dungeon; mod giant_tree; mod gnarling; mod house; +mod savannah_hut; mod savannah_pit; +mod savannah_workshop; mod sea_chapel; mod workshop; @@ -17,8 +19,9 @@ pub use self::{ adlet::AdletStronghold, bridge::Bridge, castle::Castle, citadel::Citadel, cliff_tower::CliffTower, desert_city_multiplot::DesertCityMultiPlot, desert_city_temple::DesertCityTemple, dungeon::Dungeon, giant_tree::GiantTree, - gnarling::GnarlingFortification, house::House, savannah_pit::SavannahPit, - sea_chapel::SeaChapel, workshop::Workshop, + gnarling::GnarlingFortification, house::House, savannah_hut::SavannahHut, + savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop, sea_chapel::SeaChapel, + workshop::Workshop, }; use super::*; @@ -74,5 +77,7 @@ pub enum PlotKind { CliffTower(CliffTower), Citadel(Citadel), SavannahPit(SavannahPit), + SavannahHut(SavannahHut), + SavannahWorkshop(SavannahWorkshop), Bridge(Bridge), } diff --git a/world/src/site2/plot/cliff_tower.rs b/world/src/site2/plot/cliff_tower.rs index c31b78460c..15d47e2d20 100644 --- a/world/src/site2/plot/cliff_tower.rs +++ b/world/src/site2/plot/cliff_tower.rs @@ -883,16 +883,6 @@ impl Structure for CliffTower { )) .fill(brick.clone()); } - // spawn mountaineers in each room - let spawn_pos = super_center.with_z(floor_level + 4); - let npc_amount = RandomField::new(0).get(spawn_pos) % 4; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.mountaineer", &mut rng), - ); - } } // vary next storey length += -1; diff --git a/world/src/site2/plot/savannah_hut.rs b/world/src/site2/plot/savannah_hut.rs new file mode 100644 index 0000000000..0c8f81ad63 --- /dev/null +++ b/world/src/site2/plot/savannah_hut.rs @@ -0,0 +1,266 @@ +use super::*; +use crate::{ + util::{RandomField, Sampler, CARDINALS, DIAGONALS}, + Land, +}; +use common::terrain::{BlockKind, SpriteKind}; +use rand::prelude::*; +use std::{f32::consts::TAU, sync::Arc}; +use vek::*; + +/// Represents house data generated by the `generate()` method +pub struct SavannahHut { + /// Tile position of the door tile + pub door_tile: Vec2, + /// Axis aligned bounding region for the house + bounds: Aabr, + /// Approximate altitude of the door tile + pub(crate) alt: i32, +} + +impl SavannahHut { + pub fn generate( + land: &Land, + _rng: &mut impl Rng, + site: &Site, + door_tile: Vec2, + door_dir: Vec2, + tile_aabr: Aabr, + ) -> Self { + let door_tile_pos = site.tile_center_wpos(door_tile); + let bounds = Aabr { + min: site.tile_wpos(tile_aabr.min), + max: site.tile_wpos(tile_aabr.max), + }; + Self { + bounds, + door_tile: door_tile_pos, + alt: land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32 + 2, + } + } +} + +impl Structure for SavannahHut { + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"render_savannahhut\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "render_savannahhut")] + fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) { + let base = self.alt + 1; + let center = self.bounds.center(); + let sprite_fill = Fill::Sampling(Arc::new(|wpos| { + Some(match (RandomField::new(0).get(wpos)) % 25 { + 0 => Block::air(SpriteKind::Bowl), + 1 => Block::air(SpriteKind::VialEmpty), + 2 => Block::air(SpriteKind::Lantern), + 3 => Block::air(SpriteKind::JugArabic), + 4 => Block::air(SpriteKind::Crate), + _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)), + }) + })); + let wood_dark = Fill::Brick(BlockKind::Misc, Rgb::new(142, 67, 27), 12); + let reed = Fill::Brick(BlockKind::Misc, Rgb::new(72, 55, 46), 22); + let clay = Fill::Brick(BlockKind::Misc, Rgb::new(209, 124, 57), 22); + let color = Fill::Sampling(Arc::new(|center| { + Some(match (RandomField::new(0).get(center)) % 7 { + 0 => Block::new(BlockKind::GlowingRock, Rgb::new(153, 82, 40)), + 1 => Block::new(BlockKind::GlowingRock, Rgb::new(172, 104, 57)), + 2 => Block::new(BlockKind::GlowingRock, Rgb::new(135, 106, 100)), + 3 => Block::new(BlockKind::GlowingRock, Rgb::new(198, 164, 139)), + 4 => Block::new(BlockKind::GlowingRock, Rgb::new(168, 163, 157)), + 5 => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)), + _ => Block::new(BlockKind::GlowingRock, Rgb::new(178, 124, 90)), + }) + })); + let length = (10 + RandomField::new(0).get(center.with_z(base)) % 6) as i32; + let height = 2 * length / 3; + let storeys = (1 + RandomField::new(0).get(center.with_z(base)) % 2) as i32; + let radius = length + (length / 3); + let reed_var = (1 + RandomField::new(0).get(center.with_z(base)) % 4) as f32; + let reed_parts = 36_f32 + reed_var; + let phi = TAU / reed_parts; + // roof cone + painter + .cone(Aabb { + min: (center - radius).with_z(base + (storeys * height) - (height / 2) + 1), + max: (center + radius) + .with_z(base + (storeys * height) + (height / 2) - 1 + reed_var as i32), + }) + .fill(reed.clone()); + painter + .cone(Aabb { + min: (center - radius).with_z(base + (storeys * height) - (height / 2)), + max: (center + radius) + .with_z(base + (storeys * height) + (height / 2) - 2 + reed_var as i32), + }) + .clear(); + // foundation + painter + .cylinder(Aabb { + min: (center - length).with_z(base - 3), + max: (center + length + 1).with_z(base - 2), + }) + .fill(clay.clone()); + painter + .cylinder(Aabb { + min: (center - length - 1).with_z(base - 4), + max: (center + length + 2).with_z(base - 3), + }) + .fill(clay.clone()); + painter + .cylinder(Aabb { + min: (center - length - 2).with_z(base - 5), + max: (center + length + 3).with_z(base - 4), + }) + .fill(clay.clone()); + painter + .cylinder(Aabb { + min: (center - length - 3).with_z(base - height), + max: (center + length + 4).with_z(base - 5), + }) + .fill(clay.clone()); + // room + for s in 0..storeys { + let room = painter.cylinder(Aabb { + min: (center - length + 2 + s).with_z(base - 2 + (s * height)), + max: (center + 1 + length - 2 - s).with_z(base + height + (s * height)), + }); + room.fill(clay.clone()); + // decor inlays + for dir in DIAGONALS { + let decor_pos = center + dir * (length - 2 - s); + let decor = painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + decor_pos.with_z(base - 1 + (s * (height + 2))), + 5.0, + ) + .intersect(room); + decor.fill(color.clone()); + painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + decor_pos.with_z(base - 1 + (s * (height + 2))), + 4.0, + ) + .intersect(decor) + .fill(clay.clone()); + } + } + // clear rooms + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base - 2), + max: (center + 1 + length - 4).with_z(base + (storeys * height)), + }) + .clear(); + // wood decor + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base - 1), + max: (center + 1 + length - 4).with_z(base), + }) + .fill(wood_dark.clone()); + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base), + max: (center + 1 + length - 4).with_z(base + 1), + }) + .fill(sprite_fill); + + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base + (storeys * height) - 1), + max: (center + 1 + length - 4).with_z(base + (storeys * height) + 1), + }) + .fill(wood_dark); + + for s in 0..storeys { + // entries, windows + for dir in CARDINALS { + let frame_pos = center + dir * (length - 2 - s); + let clear_pos = center + dir * (length + 2 - s); + + painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + frame_pos.with_z(base - 1 + (s * (height + 2))), + 3.0, + ) + .fill(color.clone()); + painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + clear_pos.with_z(base - 1 + (s * (height + 2))), + 2.0, + ) + .clear(); + } + } + // re clear room + painter + .cylinder(Aabb { + min: (center - length + 5).with_z(base - 2), + max: (center + 1 + length - 5).with_z(base + (storeys * height) + 1), + }) + .clear(); + // floor + painter + .cylinder(Aabb { + min: (center - (length / 2) - 1).with_z(base - 3), + max: (center + (length / 2) + 1).with_z(base - 2), + }) + .fill(color); + painter + .cylinder(Aabb { + min: (center - (length / 2) + 1).with_z(base - 3), + max: (center + (length / 2) - 1).with_z(base - 2), + }) + .fill(clay); + + // furniture + let mut sprites = vec![ + SpriteKind::DrawerSmall, + SpriteKind::ChairSingle, + SpriteKind::CoatRack, + SpriteKind::Bed, + SpriteKind::WardrobeSingle, + SpriteKind::CushionArabic, + SpriteKind::TableArabicSmall, + SpriteKind::DecorSetArabic, + SpriteKind::Bowl, + SpriteKind::VialEmpty, + SpriteKind::JugArabic, + SpriteKind::JugAndBowlArabic, + SpriteKind::SepareArabic, + ]; + let rows = if length > 12 { 2 } else { 1 }; + 'outer: for d in 0..rows { + for dir in DIAGONALS { + if sprites.is_empty() { + break 'outer; + } + let position = center + dir * (length - (9 + (d * 3))); + let sprite = sprites.swap_remove( + RandomField::new(0).get(position.with_z(base)) as usize % sprites.len(), + ); + painter.sprite(position.with_z(base - 2), sprite); + } + } + + // reed roof lines + for n in 1..=reed_parts as i32 { + let pos = Vec2::new( + center.x + ((radius as f32) * ((n as f32 * phi).cos())) as i32, + center.y + ((radius as f32) * ((n as f32 * phi).sin())) as i32, + ); + painter + .line( + pos.with_z(base + (storeys * height) - (height / 2)), + center.with_z(base + (storeys * height) + (height / 2) + reed_var as i32), + 1.0, + ) + .fill(reed.clone()); + } + } +} diff --git a/world/src/site2/plot/savannah_pit.rs b/world/src/site2/plot/savannah_pit.rs index 62153b3b20..b626d2da97 100644 --- a/world/src/site2/plot/savannah_pit.rs +++ b/world/src/site2/plot/savannah_pit.rs @@ -284,6 +284,135 @@ impl Structure for SavannahPit { max: (center + length).with_z(base - (2 * length)), }) .clear(); + // floor -1 donut room & floor -2 donut room + let rooms_clear = painter + .cylinder(Aabb { + min: (center - (4 * length)).with_z(base - (2 * length)), + max: (center + (4 * length)).with_z(base - length - 1), + }) + .union(painter.cylinder(Aabb { + min: (center - (4 * length)).with_z(base - (3 * length)), + max: (center + (4 * length)).with_z(base - (2 * length) - 1), + })); + + let rooms = painter + .cylinder(Aabb { + min: (center - (2 * length) - 2).with_z(base - (2 * length)), + max: (center + (2 * length) + 2).with_z(base - length - 1), + }) + .union(painter.cylinder(Aabb { + min: (center - length - 2).with_z(base - (3 * length)), + max: (center + length + 2).with_z(base - (2 * length) - 1), + })); + rooms_clear.without(rooms).clear(); + + // floor 0 wood ring + painter + .cylinder(Aabb { + min: (center - (3 * length)).with_z(base - length + (length / 2) - 3), + max: (center + (3 * length)).with_z(base - length + (length / 2) - 2), + }) + .fill(wood_light.clone()); + painter + .cylinder(Aabb { + min: (center - (3 * length) + 1).with_z(base - length + (length / 2) - 3), + max: (center + (3 * length) - 1).with_z(base - length + (length / 2) - 2), + }) + .clear(); + // floor -1 wood ring + painter + .cylinder(Aabb { + min: (center - (4 * length)).with_z(base - (2 * length) + (length / 2) - 3), + max: (center + (4 * length)).with_z(base - (2 * length) + (length / 2) - 2), + }) + .fill(wood_light.clone()); + painter + .cylinder(Aabb { + min: (center - (4 * length) + 1).with_z(base - (2 * length) + (length / 2) - 3), + max: (center + (4 * length) - 1).with_z(base - (2 * length) + (length / 2) - 2), + }) + .clear(); + // floor -1 wood ring sprites + painter + .cylinder(Aabb { + min: (center - (4 * length)).with_z(base - (2 * length) + (length / 2) - 2), + max: (center + (4 * length)).with_z(base - (2 * length) + (length / 2) - 1), + }) + .fill(sprite_fill.clone()); + painter + .cylinder(Aabb { + min: (center - (4 * length) + 1).with_z(base - (2 * length) + (length / 2) - 2), + max: (center + (4 * length) - 1).with_z(base - (2 * length) + (length / 2) - 1), + }) + .clear(); + // floor -1 center decor ring + painter + .cylinder(Aabb { + min: (center - (2 * length) - 2).with_z(base - (2 * length) + (length / 2) - 3), + max: (center + (2 * length) + 2).with_z(base - (2 * length) + (length / 2) - 1), + }) + .fill(color.clone()); + painter + .cylinder(Aabb { + min: (center - (2 * length)).with_z(base - (2 * length) + (length / 2) - 3), + max: (center + (2 * length)).with_z(base - (2 * length) + (length / 2) - 1), + }) + .clear(); + // floor -1 room entry + let room1_entry_pos = Vec2::new(center.x, center.y - (2 * length)); + painter + .sphere(Aabb { + min: (room1_entry_pos - 5).with_z(base - (2 * length)), + max: (room1_entry_pos + 5).with_z(base - (2 * length) + 5), + }) + .clear(); + // floor -2 wood ring + painter + .cylinder(Aabb { + min: (center - (4 * length)).with_z(base - (3 * length) + (length / 2) - 3), + max: (center + (4 * length)).with_z(base - (3 * length) + (length / 2) - 2), + }) + .fill(wood_light.clone()); + painter + .cylinder(Aabb { + min: (center - (4 * length) + 1).with_z(base - (3 * length) + (length / 2) - 3), + max: (center + (4 * length) - 1).with_z(base - (3 * length) + (length / 2) - 2), + }) + .clear(); + // floor -2 wood ring sprites + painter + .cylinder(Aabb { + min: (center - (4 * length)).with_z(base - (3 * length) + (length / 2) - 2), + max: (center + (4 * length)).with_z(base - (3 * length) + (length / 2) - 1), + }) + .fill(sprite_fill.clone()); + painter + .cylinder(Aabb { + min: (center - (4 * length) + 1).with_z(base - (3 * length) + (length / 2) - 2), + max: (center + (4 * length) - 1).with_z(base - (3 * length) + (length / 2) - 1), + }) + .clear(); + // floor -2 center decor ring + painter + .cylinder(Aabb { + min: (center - length - 2).with_z(base - (3 * length) + (length / 2) - 3), + max: (center + length + 2).with_z(base - (3 * length) + (length / 2) - 1), + }) + .fill(color); + painter + .cylinder(Aabb { + min: (center - length).with_z(base - (3 * length) + (length / 2) - 3), + max: (center + length).with_z(base - (3 * length) + (length / 2) - 1), + }) + .clear(); + // floor -2 room entry + let room2_entry_pos = Vec2::new(center.x, center.y - length); + painter + .sphere(Aabb { + min: (room2_entry_pos - 5).with_z(base - (3 * length)), + max: (room2_entry_pos + 5).with_z(base - (3 * length) + 5), + }) + .clear(); // floor stairs lamps for dir in SQUARE_4 { @@ -391,8 +520,8 @@ impl Structure for SavannahPit { .fill(wood_dark.clone()); let stair_radius2 = ((2 * length) + 1) as f32; let stairs_clear2 = painter.prim(Primitive::Cylinder(Aabb { - min: (center - (3 * length)).with_z(base - (3 * length)), - max: (center + (3 * length)).with_z(base - (2 * length)), + min: (center - length).with_z(base - (3 * length)), + max: (center + length).with_z(base - (2 * length)), })); painter .prim(Primitive::sampling( @@ -405,101 +534,6 @@ impl Structure for SavannahPit { ), )) .fill(wood_dark.clone()); - // floor -1 donut room - painter - .cylinder(Aabb { - min: (center - (4 * length)).with_z(base - (2 * length)), - max: (center + (4 * length)).with_z(base - length - 1), - }) - .without(painter.cylinder(Aabb { - min: (center - (2 * length) - 2).with_z(base - (2 * length)), - max: (center + (2 * length) + 2).with_z(base - length - 1), - })) - .clear(); - // floor -1 room entry - let room1_entry_pos = Vec2::new(center.x, center.y - (2 * length)); - painter - .sphere(Aabb { - min: (room1_entry_pos - 5).with_z(base - (2 * length)), - max: (room1_entry_pos + 5).with_z(base - (2 * length) + 5), - }) - .clear(); - - // floor -2 donut room - painter - .cylinder(Aabb { - min: (center - (4 * length)).with_z(base - (3 * length)), - max: (center + (4 * length)).with_z(base - (2 * length) - 1), - }) - .without(painter.cylinder(Aabb { - min: (center - length - 2).with_z(base - (3 * length)), - max: (center + length + 2).with_z(base - (2 * length) - 1), - })) - .clear(); - // floor -2 room entry - let room2_entry_pos = Vec2::new(center.x, center.y - length); - painter - .sphere(Aabb { - min: (room2_entry_pos - 5).with_z(base - (3 * length)), - max: (room2_entry_pos + 5).with_z(base - (3 * length) + 5), - }) - .clear(); - // floor 0 wood ring - painter - .cylinder(Aabb { - min: (center - (3 * length)).with_z(base - length + (length / 2) - 3), - max: (center + (3 * length)).with_z(base - length + (length / 2) - 2), - }) - .without(painter.cylinder(Aabb { - min: (center - (3 * length) + 1).with_z(base - length + (length / 2) - 3), - max: (center + (3 * length) - 1).with_z(base - length + (length / 2) - 2), - })) - .fill(wood_light.clone()); - // floor -1 wood ring - painter - .cylinder(Aabb { - min: (center - (4 * length)).with_z(base - (2 * length) + (length / 2) - 3), - max: (center + (4 * length)).with_z(base - (2 * length) + (length / 2) - 2), - }) - .without(painter.cylinder(Aabb { - min: (center - (4 * length) + 1).with_z(base - (2 * length) + (length / 2) - 3), - max: (center + (4 * length) - 1).with_z(base - (2 * length) + (length / 2) - 2), - })) - .fill(wood_light.clone()); - // floor -2 wood ring - painter - .cylinder(Aabb { - min: (center - (4 * length)).with_z(base - (3 * length) + (length / 2) - 3), - max: (center + (4 * length)).with_z(base - (3 * length) + (length / 2) - 2), - }) - .without(painter.cylinder(Aabb { - min: (center - (4 * length) + 1).with_z(base - (3 * length) + (length / 2) - 3), - max: (center + (4 * length) - 1).with_z(base - (3 * length) + (length / 2) - 2), - })) - .fill(wood_light.clone()); - // floor -1 wood ring sprites - painter - .cylinder(Aabb { - min: (center - (4 * length)).with_z(base - (2 * length) + (length / 2) - 2), - max: (center + (4 * length)).with_z(base - (2 * length) + (length / 2) - 1), - }) - .without(painter.cylinder(Aabb { - min: (center - (4 * length) + 1).with_z(base - (2 * length) + (length / 2) - 2), - max: (center + (4 * length) - 1).with_z(base - (2 * length) + (length / 2) - 1), - })) - .fill(sprite_fill.clone()); - // floor -2 wood ring sprites - painter - .cylinder(Aabb { - min: (center - (4 * length)).with_z(base - (3 * length) + (length / 2) - 2), - max: (center + (4 * length)).with_z(base - (3 * length) + (length / 2) - 1), - }) - .without(painter.cylinder(Aabb { - min: (center - (4 * length) + 1).with_z(base - (3 * length) + (length / 2) - 2), - max: (center + (4 * length) - 1).with_z(base - (3 * length) + (length / 2) - 1), - })) - .fill(sprite_fill.clone()); - // top wood cone painter .cone(Aabb { @@ -597,16 +631,6 @@ impl Structure for SavannahPit { max: (entries_clear + 2).with_z(base - ((1 + f) * length) + 4), }) .clear(); - // villagers in each room - let spawn_pos = (room_center - 3).with_z(base - ((1 + f) * length)); - let npc_amount = RandomField::new(0).get(spawn_pos) % 3; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } } // outside platforms painter @@ -642,16 +666,6 @@ impl Structure for SavannahPit { }) .fill(wood_dark.clone()); } - // villagers on platforms - let spawn_pos = (room_center - 2).with_z(base + length + 2); - let npc_amount = RandomField::new(0).get(spawn_pos) % 3; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } let lantern_pos = center + dir * ((4 * length) - (length / 4) + 4); painter.sprite(lantern_pos.with_z(base + length + 1), SpriteKind::Lantern); } @@ -698,16 +712,6 @@ impl Structure for SavannahPit { max: (entries_clear + 3).with_z(base - ((1 + f) * length) + 4), }) .clear(); - // villagers in each room - let spawn_pos = (room_center - 3).with_z(base - ((1 + f) * length)); - let npc_amount = RandomField::new(0).get(spawn_pos) % 3; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } // furniture match RandomField::new(0).get(room_center.with_z(base)) % 2 { 0 => { @@ -815,16 +819,6 @@ impl Structure for SavannahPit { }) .fill(wood_dark.clone()); } - // villagers on platforms - let spawn_pos = (room_center - 2).with_z(base + length + 2); - let npc_amount = RandomField::new(0).get(spawn_pos) % 3; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } let lantern_pos = center + dir * ((3 * length) - (length / 3) + 4); painter.sprite(lantern_pos.with_z(base + length + 1), SpriteKind::Lantern); } @@ -944,18 +938,6 @@ impl Structure for SavannahPit { } } } - // villagers outside on every floor - for a in 0..5 { - let spawn_pos = (center - 5).with_z(base + length - (length * a)); - let npc_amount = RandomField::new(0).get(spawn_pos) % 4; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } - } // campfire let campfire_pos = (center - 10).with_z(base); @@ -1059,13 +1041,6 @@ impl Structure for SavannahPit { max: (crate_pos + 2).with_z(base - (5 * length) + 5), }) .clear(); - // villager and lantern in each stand - let spawn_pos = (crate_pos - 1).with_z(base - (5 * length) + 3); - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); painter.sprite( (crate_pos + 1).with_z(base - (5 * length) + 3), SpriteKind::Lantern, @@ -1098,16 +1073,6 @@ impl Structure for SavannahPit { // placeholder for market items .fill(Fill::Block(Block::air(SpriteKind::Crate))); } - // villagers in market hall - let spawn_pos = (center + 10).with_z(base - (5 * length) + 4); - let npc_amount = RandomField::new(0).get(spawn_pos) % 12; - for _ in 0..npc_amount { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } // supports for dir in CARDINALS { let outer_support = center + dir * ((4 * length) - 2); @@ -1574,16 +1539,6 @@ impl Structure for SavannahPit { }) .fill(wood_dark.clone()); } - // guards and lanterns on towers - let spawn_pos = (tower_center - 2).with_z(base + (length / 2) + 2); - let npc_amount = 1 + RandomField::new(0).get(spawn_pos) % 3; - for _ in 1..(npc_amount + 1) { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.guard", &mut rng), - ); - } let lantern_pos = tower_center - 3; painter.sprite(lantern_pos.with_z(base + (length / 2)), SpriteKind::Lantern); // tunnel lanterns @@ -1643,16 +1598,6 @@ impl Structure for SavannahPit { }) .fill(wood_dark.clone()); } - // guards and lanterns on towers - let spawn_pos = (tower_center - 2).with_z(base + (length / 2) + 2); - let npc_amount = 1 + RandomField::new(0).get(spawn_pos) % 3; - for _ in 1..(npc_amount + 1) { - let mut rng = thread_rng(); - painter.spawn( - EntityInfo::at(spawn_pos.map(|e| e as f32)) - .with_asset_expect("common.entity.village.guard", &mut rng), - ); - } let lantern_pos = center + dir * (7 * wall_length); painter.sprite(lantern_pos.with_z(base + (length / 2)), SpriteKind::Lantern); // tunnel lanterns diff --git a/world/src/site2/plot/savannah_workshop.rs b/world/src/site2/plot/savannah_workshop.rs new file mode 100644 index 0000000000..8cffcf37c8 --- /dev/null +++ b/world/src/site2/plot/savannah_workshop.rs @@ -0,0 +1,308 @@ +use super::*; +use crate::{ + util::{RandomField, Sampler, CARDINALS, DIAGONALS}, + Land, +}; +use common::terrain::{BlockKind, SpriteKind}; +use rand::prelude::*; +use std::{f32::consts::TAU, sync::Arc}; +use vek::*; + +/// Represents house data generated by the `generate()` method +pub struct SavannahWorkshop { + /// Tile position of the door tile + pub door_tile: Vec2, + /// Axis aligned bounding region for the house + bounds: Aabr, + /// Approximate altitude of the door tile + pub(crate) alt: i32, +} + +impl SavannahWorkshop { + pub fn generate( + land: &Land, + _rng: &mut impl Rng, + site: &Site, + door_tile: Vec2, + door_dir: Vec2, + tile_aabr: Aabr, + ) -> Self { + let door_tile_pos = site.tile_center_wpos(door_tile); + let bounds = Aabr { + min: site.tile_wpos(tile_aabr.min), + max: site.tile_wpos(tile_aabr.max), + }; + Self { + bounds, + door_tile: door_tile_pos, + alt: land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32 + 2, + } + } +} + +impl Structure for SavannahWorkshop { + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"render_savannahworkshop\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "render_savannahworkshop")] + fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) { + let base = self.alt + 1; + let center = self.bounds.center(); + let sprite_fill = Fill::Sampling(Arc::new(|wpos| { + Some(match (RandomField::new(0).get(wpos)) % 25 { + 0 => Block::air(SpriteKind::Bowl), + 1 => Block::air(SpriteKind::VialEmpty), + 2 => Block::air(SpriteKind::Lantern), + 3 => Block::air(SpriteKind::JugArabic), + 4 => Block::air(SpriteKind::Crate), + _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)), + }) + })); + let wood_dark = Fill::Brick(BlockKind::Misc, Rgb::new(142, 67, 27), 12); + let reed = Fill::Brick(BlockKind::Misc, Rgb::new(72, 55, 46), 22); + let clay = Fill::Brick(BlockKind::Misc, Rgb::new(209, 124, 57), 22); + let color = Fill::Sampling(Arc::new(|center| { + Some(match (RandomField::new(0).get(center)) % 7 { + 0 => Block::new(BlockKind::GlowingRock, Rgb::new(153, 82, 40)), + 1 => Block::new(BlockKind::GlowingRock, Rgb::new(172, 104, 57)), + 2 => Block::new(BlockKind::GlowingRock, Rgb::new(135, 106, 100)), + 3 => Block::new(BlockKind::GlowingRock, Rgb::new(198, 164, 139)), + 4 => Block::new(BlockKind::GlowingRock, Rgb::new(168, 163, 157)), + 5 => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)), + _ => Block::new(BlockKind::GlowingRock, Rgb::new(178, 124, 90)), + }) + })); + let length = (10 + RandomField::new(0).get(center.with_z(base)) % 6) as i32; + let height = 2 * length / 3; + let storeys = (1 + RandomField::new(0).get(center.with_z(base)) % 2) as i32; + let radius = length + (length / 3); + let reed_var = (1 + RandomField::new(0).get(center.with_z(base)) % 4) as f32; + let reed_parts = 36_f32 + reed_var; + let phi = TAU / reed_parts; + // roof cone + painter + .cone(Aabb { + min: (center - radius).with_z(base + (storeys * height) - (height / 2) + 1), + max: (center + radius) + .with_z(base + (storeys * height) + (height / 2) - 1 + reed_var as i32), + }) + .fill(reed.clone()); + painter + .cone(Aabb { + min: (center - radius).with_z(base + (storeys * height) - (height / 2)), + max: (center + radius) + .with_z(base + (storeys * height) + (height / 2) - 2 + reed_var as i32), + }) + .clear(); + // foundation + painter + .cylinder(Aabb { + min: (center - length).with_z(base - 3), + max: (center + length + 1).with_z(base - 2), + }) + .fill(clay.clone()); + painter + .cylinder(Aabb { + min: (center - length - 1).with_z(base - 4), + max: (center + length + 2).with_z(base - 3), + }) + .fill(clay.clone()); + painter + .cylinder(Aabb { + min: (center - length - 2).with_z(base - 5), + max: (center + length + 3).with_z(base - 4), + }) + .fill(clay.clone()); + painter + .cylinder(Aabb { + min: (center - length - 3).with_z(base - height), + max: (center + length + 4).with_z(base - 5), + }) + .fill(clay.clone()); + // room + for s in 0..storeys { + let room = painter.cylinder(Aabb { + min: (center - length + 2 + s).with_z(base - 2 + (s * height)), + max: (center + 1 + length - 2 - s).with_z(base + height + (s * height)), + }); + room.fill(clay.clone()); + // decor inlays + for dir in DIAGONALS { + let decor_pos = center + dir * (length - 2 - s); + let decor = painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + decor_pos.with_z(base - 1 + (s * (height + 2))), + 5.0, + ) + .intersect(room); + decor.fill(color.clone()); + painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + decor_pos.with_z(base - 1 + (s * (height + 2))), + 4.0, + ) + .intersect(decor) + .fill(clay.clone()); + } + } + // clear rooms + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base - 2), + max: (center + 1 + length - 4).with_z(base + (storeys * height)), + }) + .clear(); + // wood decor + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base - 1), + max: (center + 1 + length - 4).with_z(base), + }) + .fill(wood_dark.clone()); + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base), + max: (center + 1 + length - 4).with_z(base + 1), + }) + .fill(sprite_fill); + + painter + .cylinder(Aabb { + min: (center - length + 4).with_z(base + (storeys * height) - 1), + max: (center + 1 + length - 4).with_z(base + (storeys * height) + 1), + }) + .fill(wood_dark.clone()); + + for s in 0..storeys { + // entries, windows + for dir in CARDINALS { + let frame_pos = center + dir * (length - 2 - s); + let clear_pos = center + dir * (length + 2 - s); + + painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + frame_pos.with_z(base - 1 + (s * (height + 2))), + 3.0, + ) + .fill(color.clone()); + painter + .line( + center.with_z(base - 1 + (s * (height + 2))), + clear_pos.with_z(base - 1 + (s * (height + 2))), + 2.0, + ) + .clear(); + } + } + // re clear room + painter + .cylinder(Aabb { + min: (center - length + 5).with_z(base - 2), + max: (center + 1 + length - 5).with_z(base + (storeys * height) + 1), + }) + .clear(); + // floor + painter + .cylinder(Aabb { + min: (center - (length / 2) - 1).with_z(base - 3), + max: (center + (length / 2) + 1).with_z(base - 2), + }) + .fill(color); + painter + .cylinder(Aabb { + min: (center - (length / 2) + 1).with_z(base - 3), + max: (center + (length / 2) - 1).with_z(base - 2), + }) + .fill(clay.clone()); + + // reed roof lines + + for n in 1..=reed_parts as i32 { + let pos = Vec2::new( + center.x + ((radius as f32) * ((n as f32 * phi).cos())) as i32, + center.y + ((radius as f32) * ((n as f32 * phi).sin())) as i32, + ); + painter + .line( + pos.with_z(base + (storeys * height) - (height / 2)), + center.with_z(base + (storeys * height) + (height / 2) + reed_var as i32), + 1.0, + ) + .fill(reed.clone()); + } + // chimney + painter + .cylinder(Aabb { + min: (center - 3) + .with_z(base - 1 + (storeys * height) + (height / 2) + reed_var as i32), + max: (center + 4) + .with_z(base + (storeys * height) + (height / 2) + reed_var as i32), + }) + .fill(wood_dark); + // clear chimney + painter + .cylinder(Aabb { + min: (center - 2).with_z(base + (storeys * height) - 4), + max: (center + 3) + .with_z(base + 5 * (storeys * height) + (height / 2) + reed_var as i32), + }) + .clear(); + + painter + .cylinder(Aabb { + min: (center - 1).with_z(base - 2), + max: (center + 2).with_z(base - 1), + }) + .fill(clay); + painter + .aabb(Aabb { + min: (center).with_z(base - 2), + max: (center + 1).with_z(base - 1), + }) + .clear(); + painter + .aabb(Aabb { + min: (center).with_z(base - 3), + max: (center + 1).with_z(base - 2), + }) + .fill(Fill::Block(Block::air(SpriteKind::Ember))); + + let mut stations = vec![ + SpriteKind::CraftingBench, + SpriteKind::Forge, + SpriteKind::SpinningWheel, + SpriteKind::TanningRack, + SpriteKind::CookingPot, + SpriteKind::Cauldron, + SpriteKind::Loom, + SpriteKind::Anvil, + SpriteKind::DismantlingBench, + SpriteKind::RepairBench, + ]; + let cr_pos = stations.len() as f32; + let phi = TAU / cr_pos; + 'outer: for d in 0..2 { + let dist = 4 + d; + for n in 1..=cr_pos as i32 { + let pos = Vec2::new( + center.x + ((dist as f32) * ((n as f32 * phi).cos())) as i32, + center.y + ((dist as f32) * ((n as f32 * phi).sin())) as i32, + ); + if stations.is_empty() { + break 'outer; + } + let cr_station = stations.swap_remove( + RandomField::new(0).get(pos.with_z(base)) as usize % stations.len(), + ); + painter.sprite(pos.with_z(base - 2), cr_station); + } + } + + painter.spawn( + EntityInfo::at((center).with_z(base - 2).map(|e| e as f32 + 0.5)).into_waypoint(), + ); + } +}