diff --git a/assets/voxygen/element/ui/map/buttons/adlet.png b/assets/voxygen/element/ui/map/buttons/adlet.png new file mode 100644 index 0000000000..df6d06ab0b --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/adlet.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a38a3d9d487f491c1dd9f548ac925abb82767b2b9a679405dc3fa240dced10e2 +size 10475 diff --git a/assets/voxygen/element/ui/map/buttons/adlet_bg.png b/assets/voxygen/element/ui/map/buttons/adlet_bg.png new file mode 100644 index 0000000000..798da26aad --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/adlet_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88e0efb6af9050b880651c9c858fb827c9a805d95b2ce93379bb1a8ff396c2ac +size 2880 diff --git a/assets/voxygen/element/ui/map/buttons/adlet_hover.png b/assets/voxygen/element/ui/map/buttons/adlet_hover.png new file mode 100644 index 0000000000..cd7b9aa693 --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/adlet_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f06c8ba6af09babdcfb960d75a384f5aab8e03f1079de3708ed98f74dfd06ef0 +size 3301 diff --git a/assets/world/manifests/site_structures/adlet/igloo.ron b/assets/world/manifests/site_structures/adlet/igloo.ron new file mode 100644 index 0000000000..c391e400f5 --- /dev/null +++ b/assets/world/manifests/site_structures/adlet/igloo.ron @@ -0,0 +1,8 @@ +#![enable(unwrap_newtypes)] + +[ + ( + specifier: "world.structure.natural.igloo", + center: (14, 13, 3), + ), +] diff --git a/common/net/src/msg/world_msg.rs b/common/net/src/msg/world_msg.rs index c379ae0283..254bd69269 100644 --- a/common/net/src/msg/world_msg.rs +++ b/common/net/src/msg/world_msg.rs @@ -150,6 +150,7 @@ pub enum SiteKind { Gnarling, ChapelSite, Bridge, + Adlet, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index e171d538e6..f3aa7b33ad 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -532,6 +532,9 @@ image_ids! { mmap_site_gnarling: "voxygen.element.ui.map.buttons.gnarling", mmap_site_gnarling_hover: "voxygen.element.ui.map.buttons.gnarling_hover", mmap_site_gnarling_bg: "voxygen.element.ui.map.buttons.gnarling_bg", + mmap_site_adlet: "voxygen.element.ui.map.buttons.adlet", + mmap_site_adlet_hover: "voxygen.element.ui.map.buttons.adlet_hover", + mmap_site_adlet_bg: "voxygen.element.ui.map.buttons.adlet_bg", mmap_site_minotaur: "voxygen.element.ui.map.buttons.minotaur", mmap_site_minotaur_hover: "voxygen.element.ui.map.buttons.minotaur_hover", mmap_site_minotaur_bg: "voxygen.element.ui.map.buttons.minotaur_bg", diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 1a94317184..be1fced258 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -924,6 +924,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Gnarling => i18n.get_msg("hud-map-gnarling"), SiteKind::ChapelSite => i18n.get_msg("hud-map-chapel_Site"), SiteKind::Bridge => i18n.get_msg("hud-map-bridge"), + SiteKind::Adlet => i18n.get_msg("hud.map.adlet"), }); let (difficulty, desc) = match &site.kind { SiteKind::Town => (None, i18n.get_msg("hud-map-town")), @@ -950,6 +951,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Gnarling => (Some(0), i18n.get_msg("hud-map-gnarling")), SiteKind::ChapelSite => (Some(3), i18n.get_msg("hud-map-chapel_site")), SiteKind::Bridge => (None, i18n.get_msg("hud-map-bridge")), + SiteKind::Adlet => (Some(0), i18n.get_msg("hud.map.adlet")), }; let desc = desc.into_owned() + &get_site_economy(site_rich); let site_btn = Button::image(match &site.kind { @@ -959,6 +961,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Cave => self.imgs.mmap_site_cave, SiteKind::Tree => self.imgs.mmap_site_tree, SiteKind::Gnarling => self.imgs.mmap_site_gnarling, + SiteKind::Adlet => self.imgs.mmap_site_adlet, SiteKind::Dungeon { difficulty } => match difficulty { 4 => self.imgs.mmap_site_minotaur, 5 => self.imgs.mmap_site_mindflayer, @@ -979,6 +982,7 @@ impl<'a> Widget for Map<'a> { SiteKind::Cave => self.imgs.mmap_site_cave_hover, SiteKind::Tree => self.imgs.mmap_site_tree_hover, SiteKind::Gnarling => self.imgs.mmap_site_gnarling_hover, + SiteKind::Adlet => self.imgs.mmap_site_adlet_hover, SiteKind::Dungeon { difficulty } => match difficulty { 4 => self.imgs.mmap_site_minotaur_hover, 5 => self.imgs.mmap_site_mindflayer_hover, @@ -995,15 +999,16 @@ impl<'a> Widget for Map<'a> { match &site.kind { SiteKind::Town => TEXT_COLOR, SiteKind::Castle => TEXT_COLOR, - SiteKind::Dungeon { .. } | SiteKind::Gnarling | SiteKind::ChapelSite => { - match difficulty { - Some(0) => QUALITY_LOW, - Some(1) => QUALITY_COMMON, - Some(2) => QUALITY_MODERATE, - Some(3) => QUALITY_HIGH, - Some(4 | 5) => QUALITY_EPIC, - _ => TEXT_COLOR, - } + SiteKind::Dungeon { .. } + | SiteKind::Gnarling + | SiteKind::ChapelSite + | SiteKind::Adlet => match difficulty { + Some(0) => QUALITY_LOW, + Some(1) => QUALITY_COMMON, + Some(2) => QUALITY_MODERATE, + Some(3) => QUALITY_HIGH, + Some(4 | 5) => QUALITY_EPIC, + _ => TEXT_COLOR, }, SiteKind::Cave => TEXT_COLOR, SiteKind::Tree => TEXT_COLOR, @@ -1022,9 +1027,10 @@ impl<'a> Widget for Map<'a> { // Only display sites that are toggled on let show_site = match &site.kind { SiteKind::Town => show_towns, - SiteKind::Dungeon { .. } | SiteKind::Gnarling | SiteKind::ChapelSite => { - show_dungeons - }, + SiteKind::Dungeon { .. } + | SiteKind::Gnarling + | SiteKind::ChapelSite + | SiteKind::Adlet => show_dungeons, SiteKind::Castle => show_castles, SiteKind::Cave => show_caves, SiteKind::Tree => show_trees, @@ -1081,7 +1087,10 @@ impl<'a> Widget for Map<'a> { dif_img.set(state.ids.site_difs[i], ui) } }, - SiteKind::Dungeon { .. } | SiteKind::Gnarling | SiteKind::ChapelSite => { + SiteKind::Dungeon { .. } + | SiteKind::Gnarling + | SiteKind::ChapelSite + | SiteKind::Adlet => { if show_dungeons { dif_img.set(state.ids.site_difs[i], ui) } diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index 8cfcae7930..bca1be959a 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -701,6 +701,7 @@ impl<'a> Widget for MiniMap<'a> { SiteKind::Tree => None, SiteKind::Gnarling => Some(0), SiteKind::Bridge => None, + SiteKind::Adlet => Some(1), }; Image::new(match &site.kind { @@ -712,6 +713,7 @@ impl<'a> Widget for MiniMap<'a> { SiteKind::Tree => self.imgs.mmap_site_tree, SiteKind::Gnarling => self.imgs.mmap_site_gnarling_bg, SiteKind::Bridge => self.imgs.mmap_site_bridge_bg, + SiteKind::Adlet => self.imgs.mmap_site_adlet_bg, }) .x_y_position_relative_to( state.ids.map_layers[0], @@ -738,6 +740,7 @@ impl<'a> Widget for MiniMap<'a> { SiteKind::Tree => self.imgs.mmap_site_tree, SiteKind::Gnarling => self.imgs.mmap_site_gnarling, SiteKind::Bridge => self.imgs.mmap_site_bridge, + SiteKind::Adlet => self.imgs.mmap_site_adlet, }) .middle_of(state.ids.mmap_site_icons_bgs[i]) .w_h(20.0, 20.0) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index e4cc771cc5..8181c02b8f 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -248,6 +248,10 @@ impl Civs { )?, SiteKind::ChapelSite, ), + 44..=49 => ( + find_site_loc(&mut ctx, (&this.gnarling_enemies(), 40), SiteKind::Adlet)?, + SiteKind::Adlet, + ), _ => ( find_site_loc( &mut ctx, @@ -289,6 +293,7 @@ impl Civs { SiteKind::Gnarling => (16i32, 10.0), SiteKind::Citadel => (16i32, 0.0), SiteKind::Bridge(_, _) => (0, 0.0), + SiteKind::Adlet => (16i32, 10.0), }; let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind { @@ -420,6 +425,11 @@ impl Civs { *a, *b, )), + SiteKind::Adlet => WorldSite::adlet(site2::Site::generate_adlet( + &Land::from_sim(ctx.sim), + &mut rng, + wpos, + )), } }); sim_site.site_tmp = Some(site); @@ -1260,6 +1270,13 @@ impl Civs { }) } + fn adlet_enemies(&self) -> Vec> { + self.sites().filter_map(|s| match s.kind { + SiteKind::Tree | SiteKind::GiantTree => None, + _ => Some(s.center), + }) + } + fn chapel_site_enemies(&self) -> impl Iterator> + '_ { self.sites().filter_map(|s| match s.kind { SiteKind::Tree | SiteKind::GiantTree => None, @@ -1545,6 +1562,7 @@ pub enum SiteKind { Gnarling, Citadel, Bridge(Vec2, Vec2), + Adlet, } impl SiteKind { @@ -1672,6 +1690,7 @@ impl SiteKind { && (-0.3..0.4).contains(&chunk.temp) && chunk.tree_density > 0.75 }, + SiteKind::Adlet => (-0.8..0.2).contains(&chunk.temp) && chunk.tree_density > 0.2, SiteKind::GiantTree | SiteKind::Tree => { on_land() && on_flat_terrain() diff --git a/world/src/lib.rs b/world/src/lib.rs index 5031773127..2ef6f778cf 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -177,6 +177,7 @@ impl World { civ::SiteKind::ChapelSite => world_msg::SiteKind::ChapelSite, civ::SiteKind::Citadel => world_msg::SiteKind::Castle, civ::SiteKind::Bridge(_, _) => world_msg::SiteKind::Bridge, + civ::SiteKind::Adlet => world_msg::SiteKind::Adlet, }, wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), } diff --git a/world/src/site/economy/context.rs b/world/src/site/economy/context.rs index b9e427c363..4db0cece91 100644 --- a/world/src/site/economy/context.rs +++ b/world/src/site/economy/context.rs @@ -129,6 +129,7 @@ impl Environment { SiteKind::Tree(_) => (), SiteKind::GiantTree(_) => (), SiteKind::Gnarling(_) => {}, + SiteKind::Adlet(_) => {}, SiteKind::ChapelSite(_) => {}, SiteKind::Bridge(_) => {}, } diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index b25d7e2e7c..8dec1e98c1 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -74,6 +74,7 @@ pub enum SiteKind { GiantTree(site2::Site), Gnarling(site2::Site), Bridge(site2::Site), + Adlet(site2::Site), } impl Site { @@ -98,6 +99,13 @@ impl Site { } } + pub fn adlet(g: site2::Site) -> Self { + Self { + kind: SiteKind::Adlet(g), + economy: Economy::default(), + } + } + pub fn castle(c: Castle) -> Self { Self { kind: SiteKind::Castle(c), @@ -175,6 +183,7 @@ impl Site { SiteKind::GiantTree(gt) => gt.radius(), SiteKind::Gnarling(g) => g.radius(), SiteKind::Bridge(b) => b.radius(), + SiteKind::Adlet(g) => g.radius(), } } @@ -192,6 +201,7 @@ impl Site { SiteKind::GiantTree(gt) => gt.origin, SiteKind::Gnarling(g) => g.origin, SiteKind::Bridge(b) => b.origin, + SiteKind::Adlet(g) => g.origin, } } @@ -209,6 +219,7 @@ impl Site { SiteKind::GiantTree(gt) => gt.spawn_rules(wpos), SiteKind::Gnarling(g) => g.spawn_rules(wpos), SiteKind::Bridge(b) => b.spawn_rules(wpos), + SiteKind::Adlet(g) => g.spawn_rules(wpos), } } @@ -226,6 +237,7 @@ impl Site { SiteKind::GiantTree(gt) => gt.name(), SiteKind::Gnarling(g) => g.name(), SiteKind::Bridge(b) => b.name(), + SiteKind::Adlet(g) => g.name(), } } @@ -262,6 +274,7 @@ impl Site { SiteKind::GiantTree(gt) => gt.render(canvas, dynamic_rng), SiteKind::Gnarling(g) => g.render(canvas, dynamic_rng), SiteKind::Bridge(b) => b.render(canvas, dynamic_rng), + SiteKind::Adlet(g) => g.render(canvas, dynamic_rng), } } @@ -292,6 +305,7 @@ impl Site { SiteKind::GiantTree(gt) => gt.apply_supplement(dynamic_rng, wpos2d, supplement), SiteKind::Gnarling(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement), SiteKind::Bridge(b) => b.apply_supplement(dynamic_rng, wpos2d, supplement), + SiteKind::Adlet(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement), } } diff --git a/world/src/site/namegen.rs b/world/src/site/namegen.rs index a9d5453556..f5449310cb 100644 --- a/world/src/site/namegen.rs +++ b/world/src/site/namegen.rs @@ -643,4 +643,24 @@ impl<'a, R: Rng> NameGen<'a, R> { ]; self.generate_theme_from_parts(&start, &middle, &vowel, &end) } + + //adlet placeholder + pub fn generate_adlet(mut self) -> String { + let start = ["gn", "kr", "k", "r", "t", "kn", "tr", "kt", "gr"]; + let middle = [ + "t", "tt", "k", "kk", "r", "r", "rl", "lm", "km", "tm", "kn", "kr", "tr", "nk", "gn", + "kl", "kt", "lt", "arln", "ln", "k't", "k'n", "k'm", "g'm", "l'k", "t'n", "r'k", + "n'kr", "k R", "t K", "rl Gn", "rl K", "k Gn", "t M", "t N", "r K", "r N", "k M", + "k T", "rl T", "t Kn", "r Kn", + ]; + let vowel = ["e", "a", "i", "o"]; + let end = [ + "arak", "orok", "arok", "orak", "attak", "akarl", "okarl", "atok", "anak", "etak", + "orek", "arek", "atik", "arik", "etik", "arlak", "arlek", "otek", "almek", "arlnok", + "arlnak", "okorl", "eknok", "ottok", "erlek", "akkat", "okkar", "attor", "ittor", + "aktor", "okomor", "imor", "inork", "inor", "amakkor", "ikkor", "amarl", "omarl", + "ikkarl", "okkarl", "emekk", "akatak", "okatak", + ]; + self.generate_theme_from_parts(&start, &middle, &vowel, &end) + } } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 707f786eb7..ba6780675c 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -105,6 +105,7 @@ impl Site { .filter_map(|plot| match &plot.kind { PlotKind::Dungeon(d) => Some(d.spawn_rules(wpos)), PlotKind::Gnarling(g) => Some(g.spawn_rules(wpos)), + PlotKind::Adlet(g) => Some(g.spawn_rules(wpos)), _ => None, }) .fold(base_spawn_rules, |a, b| a.combine(b)) @@ -470,6 +471,34 @@ impl Site { site } + pub fn generate_adlet(land: &Land, rng: &mut impl Rng, origin: Vec2) -> Self { + let mut rng = reseed(rng); + let mut site = Site { + origin, + ..Site::default() + }; + site.demarcate_obstacles(land); + let adlet_stronghold = plot::AdletStronghold::generate(origin, land, &mut rng); + site.name = adlet_stronghold.name().to_string(); + let size = adlet_stronghold.radius() / tile::TILE_SIZE as i32; + let aabr = Aabr { + min: Vec2::broadcast(-size), + max: Vec2::broadcast(size), + }; + let plot = site.create_plot(Plot { + kind: PlotKind::Adlet(adlet_stronghold), + root_tile: aabr.center(), + tiles: aabr_tiles(aabr).collect(), + seed: rng.gen(), + }); + site.blit_aabr(aabr, Tile { + kind: TileKind::AdletStronghold, + plot: Some(plot), + hard_alt: None, + }); + site + } + pub fn generate_giant_tree(land: &Land, rng: &mut impl Rng, origin: Vec2) -> Self { let mut rng = reseed(rng); let mut site = Site { @@ -1341,6 +1370,7 @@ impl Site { PlotKind::SeaChapel(sea_chapel) => sea_chapel.render_collect(self, canvas), PlotKind::Dungeon(dungeon) => dungeon.render_collect(self, canvas), PlotKind::Gnarling(gnarling) => gnarling.render_collect(self, canvas), + PlotKind::Adlet(adlet) => adlet.render_collect(self, canvas), 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), @@ -1420,6 +1450,7 @@ impl Site { match &plot.kind { PlotKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, supplement), PlotKind::Gnarling(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement), + PlotKind::Adlet(g) => g.apply_supplement(dynamic_rng, wpos2d, supplement), _ => {}, } } diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 6afb2f1ba5..e347e191fa 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -1,3 +1,4 @@ +mod adlet; mod bridge; mod castle; mod citadel; @@ -13,10 +14,11 @@ mod sea_chapel; mod workshop; pub use self::{ - 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, + 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, }; use super::*; @@ -67,6 +69,7 @@ pub enum PlotKind { Road(Path>), Dungeon(Dungeon), Gnarling(GnarlingFortification), + Adlet(AdletStronghold), GiantTree(GiantTree), CliffTower(CliffTower), Citadel(Citadel), diff --git a/world/src/site2/plot/adlet.rs b/world/src/site2/plot/adlet.rs new file mode 100644 index 0000000000..2f4fddb85b --- /dev/null +++ b/world/src/site2/plot/adlet.rs @@ -0,0 +1,2111 @@ +use super::*; +use crate::{ + assets::AssetHandle, + site2::{gen::PrimitiveTransform, util::Dir}, + util::{attempt, sampler::Sampler, RandomField}, + Land, +}; +use common::{ + generation::{ChunkSupplement, EntityInfo}, + terrain::{Structure as PrefabStructure, StructuresGroup}, +}; +use kiddo::{distance::squared_euclidean, KdTree}; +use lazy_static::lazy_static; +use rand::prelude::*; +use std::collections::HashMap; +use vek::*; + +pub struct AdletStronghold { + name: String, + seed: u32, + origin: Vec2, + radius: i32, + wall_radius: i32, + wall_segments: Vec<(Vec2, Vec2)>, + wall_towers: Vec>, + // Structure indicates the kind of structure it is, vec2 is relative position of a hut compared + // to origin, ori tells which way structure should face + structure_locations: Vec<(AdletStructure, Vec3, Dir)>, + tunnels: Tunnels, +} + +enum AdletStructure { + Hut, + VeloriteHut, + Igloo, + ChieftainHut, + WatchTower, + Banner, +} + +impl AdletStructure { + fn required_separation(&self, other: &Self) -> i32 { + let radius = |structure: &Self| match structure { + Self::Hut => 7, + Self::VeloriteHut => 15, + Self::Banner => 6, + Self::Igloo => 10, + // Generated in different pass that doesn't use distance check + Self::WatchTower => 0, + Self::ChieftainHut => 0, + }; + + let additional_padding = match (self, other) { + (Self::Banner, Self::Banner) => 50, + (Self::Igloo, Self::Igloo) => 50, + (Self::VeloriteHut, Self::VeloriteHut) => 50, + _ => 0, + }; + + radius(self) + radius(other) + additional_padding + } +} + +impl AdletStronghold { + pub fn generate(wpos: Vec2, land: &Land, rng: &mut impl Rng) -> Self { + let rpos_height = |rpos| land.get_alt_approx(rpos + wpos) as i32; + + let name = NameGen::location(rng).generate_adlet(); + let seed = rng.gen(); + let origin = wpos; + + let wall_radius = { + let unit_size = rng.gen_range(10..20); + let num_units = rng.gen_range(5..10); + let variation = rng.gen_range(0..50); + unit_size * num_units + variation + }; + + let radius = wall_radius + 50; + + // Tunnels + let alt = land.get_alt_approx(wpos) as i32; + let start = wpos.with_z(alt); + let boss_room_shift = rng.gen_range(60..110); + let end_xy = match rng.gen_range(0..4) { + 0 => Vec2::new(start.x + boss_room_shift, start.y + boss_room_shift), + 1 => Vec2::new(start.x - boss_room_shift, start.y + boss_room_shift), + 2 => Vec2::new(start.x - boss_room_shift, start.y - boss_room_shift), + 3 => Vec2::new(start.x + boss_room_shift, start.y - boss_room_shift), + // Unreachable + _ => unreachable!(), + }; + + let is_underground = |pos: Vec3| land.get_alt_approx(pos.xy()) as i32 - 9 > pos.z; + let is_valid_edge = |p1: Vec3, p2: Vec3| { + let diff = p1 - p2; + // Check that the new point is underground and the slope is mildish + is_underground(p2) && (diff.z.pow(2) / diff.xy().magnitude_squared().max(1)) < 3 + }; + let mut end = end_xy.with_z(start.z - 20); + for i in 1..31 { + let new_z = start.z - (i * 20); + if is_underground(end_xy.with_z(new_z + 35)) { + end = end_xy.with_z(new_z); + break; + } + } + + let tunnel_length_range = (12.0, 27.0); + let tunnels = + Tunnels::new(start, end, is_valid_edge, tunnel_length_range, rng).unwrap_or_default(); + + let num_points = (wall_radius / 15).max(5); + let outer_wall_corners = (0..num_points) + .into_iter() + .map(|a| { + let angle = a as f32 / num_points as f32 * core::f32::consts::TAU; + Vec2::new(angle.cos(), angle.sin()).map(|a| (a * wall_radius as f32) as i32) + }) + .map(|point| { + point.map(|a| { + let variation = wall_radius / 5; + a + rng.gen_range(-variation..=variation) + }) + }) + .collect::>(); + + let gate_index = rng.gen_range(0..outer_wall_corners.len()); + + let chieftain_indices = { + // Will not be adjacent to gate and needs two sections, so subtract 4 (3 to get + // rid of gate and adjacent, 1 to account for taking 2 sections) + let chosen = rng.gen_range(0..(outer_wall_corners.len() - 4)); + let index = if gate_index < 2 { + chosen + gate_index + 2 + } else if chosen < (gate_index - 2) { + chosen + } else { + chosen + 4 + }; + [index, (index + 1) % outer_wall_corners.len()] + }; + + // TODO: Figure out how to resolve the allow + #[allow(clippy::needless_collect)] + let outer_wall_segments = outer_wall_corners + .iter() + .enumerate() + .filter_map(|(i, point)| { + if i == gate_index { + None + } else { + let next_point = if let Some(point) = outer_wall_corners.get(i + 1) { + *point + } else { + outer_wall_corners[0] + }; + Some((*point, next_point)) + } + }) + .collect::>(); + + // Structures will not spawn in wall corner triangles corresponding to these + // indices + let forbidden_indices = [gate_index, chieftain_indices[0], chieftain_indices[1]]; + // Structures will be weighted to spawn further from these indices when + // selecting a point in the triangle + let restricted_indices = [ + (chieftain_indices[0] + outer_wall_corners.len() - 1) % outer_wall_corners.len(), + (chieftain_indices[1] + 1) % outer_wall_corners.len(), + ]; + + let desired_structures = wall_radius.pow(2) / 100; + let mut structure_locations = Vec::<(AdletStructure, Vec3, Dir)>::new(); + for _ in 0..desired_structures { + if let Some((hut_loc, kind)) = attempt(50, || { + // Choose structure kind + let structure_kind = match rng.gen_range(0..10) { + 0 => AdletStructure::Igloo, + 1..=3 => AdletStructure::VeloriteHut, + 4..=5 => AdletStructure::Banner, + _ => AdletStructure::Hut, + }; + + // Choose triangle + let corner_1_index = rng.gen_range(0..outer_wall_corners.len()); + + if forbidden_indices.contains(&corner_1_index) { + return None; + } + + let center = Vec2::zero(); + let corner_1 = outer_wall_corners[corner_1_index]; + let (corner_2, corner_2_index) = + if let Some(corner) = outer_wall_corners.get(corner_1_index + 1) { + (*corner, corner_1_index + 1) + } else { + (outer_wall_corners[0], 0) + }; + + let center_weight: f32 = rng.gen_range(0.15..0.7); + + // Forbidden and restricted indices are near walls, so don't spawn structures + // too close to avoid overlap with wall + let corner_1_weight_range = if restricted_indices.contains(&corner_1_index) { + let limit = 0.75; + if chieftain_indices.contains(&corner_2_index) { + ((1.0 - center_weight) * (1.0 - limit))..(1.0 - center_weight) + } else { + 0.0..((1.0 - center_weight) * limit) + } + } else { + 0.0..(1.0 - center_weight) + }; + + let corner_1_weight = rng.gen_range(corner_1_weight_range); + let corner_2_weight = 1.0 - center_weight - corner_1_weight; + + let structure_center: Vec2 = (center * center_weight + + corner_1.as_() * corner_1_weight + + corner_2.as_() * corner_2_weight) + .as_(); + + // Check that structure not in the water or too close to another structure + if land + .get_chunk_wpos(structure_center + origin) + .map_or(false, |c| c.is_underwater()) + || structure_locations.iter().any(|(kind, loc, _door_dir)| { + structure_center.distance_squared(loc.xy()) + < structure_kind.required_separation(kind).pow(2) + }) + { + None + } else { + Some(( + structure_center.with_z(rpos_height(structure_center)), + structure_kind, + )) + } + }) { + let dir_to_center = Dir::from_vector(hut_loc.xy()).opposite(); + let door_rng: u32 = rng.gen_range(0..9); + let door_dir = match door_rng { + 0..=3 => dir_to_center, + 4..=5 => dir_to_center.rotated_cw(), + 6..=7 => dir_to_center.rotated_ccw(), + // Should only be 8 + _ => dir_to_center.opposite(), + }; + structure_locations.push((kind, hut_loc, door_dir)); + } + } + + let wall_connections = [ + outer_wall_corners[chieftain_indices[0]], + outer_wall_corners[(chieftain_indices[1] + 1) % outer_wall_corners.len()], + ]; + let inner_tower_locs = wall_connections + .iter() + .map(|corner_pos| *corner_pos / 3) + .collect::>(); + + let chieftain_hut_loc = ((inner_tower_locs[0] + inner_tower_locs[1]) + + 2 * outer_wall_corners[chieftain_indices[1]]) + / 4; + let chieftain_hut_ori = Dir::from_vector(chieftain_hut_loc).opposite(); + structure_locations.push(( + AdletStructure::ChieftainHut, + chieftain_hut_loc.with_z(rpos_height(chieftain_hut_loc)), + chieftain_hut_ori, + )); + + let watchtower_locs = { + let (corner_1, corner_2) = ( + outer_wall_corners[gate_index], + outer_wall_corners[(gate_index + 1) % outer_wall_corners.len()], + ); + [ + corner_1 / 5 + corner_2 * 4 / 5, + corner_1 * 4 / 5 + corner_2 / 5, + ] + }; + watchtower_locs.iter().for_each(|loc| { + structure_locations.push(( + AdletStructure::WatchTower, + loc.with_z(rpos_height(*loc)), + Dir::Y, + )); + }); + + let wall_towers = outer_wall_corners + .into_iter() + .chain(inner_tower_locs.iter().copied()) + .map(|pos_2d| pos_2d.with_z(rpos_height(pos_2d))) + .collect::>(); + let wall_segments = outer_wall_segments + .into_iter() + .chain( + wall_connections + .iter() + .copied() + .zip(inner_tower_locs.into_iter()), + ) + .collect::>(); + + Self { + name, + seed, + origin, + radius, + wall_radius, + wall_towers, + wall_segments, + structure_locations, + tunnels, + } + } + + pub fn name(&self) -> &str { &self.name } + + pub fn radius(&self) -> i32 { self.radius } + + pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { + SpawnRules { + trees: wpos.distance_squared(self.origin) > self.wall_radius.pow(2), + waypoints: false, + ..SpawnRules::default() + } + } + + // TODO: Find a better way of spawning entities in site2 + pub fn apply_supplement<'a>( + &'a self, + // NOTE: Used only for dynamic elements like chests and entities! + dynamic_rng: &mut impl Rng, + wpos2d: Vec2, + supplement: &mut ChunkSupplement, + ) { + let rpos = wpos2d - self.origin; + let area = Aabr { + min: rpos, + max: rpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + }; + + for terminal in &self.tunnels.terminals { + if area.contains_point(terminal.xy() - self.origin) { + let chance = dynamic_rng.gen_range(0..10); + let entities = match chance { + 0..=4 => vec![mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng)], + 5 => vec![ + mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + ], + 6 => vec![deadwood(*terminal - 5 * Vec3::unit_z(), dynamic_rng)], + 7 => vec![ + mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + deadwood(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + ], + 8 => vec![ + mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + mandragora(*terminal - 5 * Vec3::unit_z(), dynamic_rng), + ], + _ => Vec::new(), + }; + for entity in entities { + supplement.add_entity(entity) + } + } + } + if area.contains_point(self.tunnels.end.xy() - self.origin) { + supplement.add_entity(harvester_boss( + self.tunnels.end - 5 * Vec3::unit_z(), + dynamic_rng, + )); + } + + for (loc, pos, _ori) in &self.structure_locations { + let wpos = *pos + self.origin; + if area.contains_point(pos.xy()) { + match loc { + AdletStructure::Hut => { + let num = dynamic_rng.gen_range(1..=3); + for _ in 0..num { + supplement.add_entity(random_gnarling(wpos, dynamic_rng)); + } + }, + AdletStructure::VeloriteHut => { + let num = dynamic_rng.gen_range(1..=4); + for _ in 0..num { + supplement.add_entity(random_gnarling( + wpos.xy().with_z(wpos.z + 12), + dynamic_rng, + )); + } + }, + AdletStructure::Banner => {}, + AdletStructure::ChieftainHut => { + let pos = wpos.xy().with_z(wpos.z + 8); + supplement.add_entity(gnarling_chieftain(pos, dynamic_rng)); + for _ in 0..2 { + supplement.add_entity(wood_golem(pos, dynamic_rng)); + } + for _ in 0..6 { + supplement.add_entity(random_gnarling(pos, dynamic_rng)); + } + }, + AdletStructure::WatchTower => { + supplement.add_entity(wood_golem(wpos, dynamic_rng)); + let spawn_pos = wpos.xy().with_z(wpos.z + 27); + let num = dynamic_rng.gen_range(2..=4); + for _ in 0..num { + supplement.add_entity(gnarling_stalker( + spawn_pos + Vec2::broadcast(4), + dynamic_rng, + )); + } + }, + AdletStructure::Igloo => {}, + } + } + } + + for pos in &self.wall_towers { + let wpos = *pos + self.origin; + if area.contains_point(pos.xy()) { + for _ in 0..4 { + supplement + .add_entity(gnarling_stalker(wpos.xy().with_z(wpos.z + 21), dynamic_rng)) + } + } + } + } +} + +impl Structure for AdletStronghold { + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"render_adletstronghold\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "render_adletstronghold")] + fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) { + // Create outer wall + for (point, next_point) in self.wall_segments.iter() { + // This adds additional points for the wall on the line between two points, + // allowing the wall to better handle slopes + const SECTIONS_PER_WALL_SEGMENT: usize = 3; + + (0..(SECTIONS_PER_WALL_SEGMENT as i32)) + .into_iter() + .map(move |a| { + let get_point = + |a| point + (next_point - point) * a / (SECTIONS_PER_WALL_SEGMENT as i32); + (get_point(a), get_point(a + 1)) + }) + .for_each(|(point, next_point)| { + // 2d world positions of each point in wall segment + let point = point; + let start_wpos = point + self.origin; + let end_wpos = next_point + self.origin; + + let lightstone = Fill::Brick(BlockKind::Wood, Rgb::new(107, 107, 107), 18); + let midstone = Fill::Brick(BlockKind::Wood, Rgb::new(70, 70, 70), 18); + let darkstone = Fill::Brick(BlockKind::Wood, Rgb::new(42, 42, 42), 18); + let darkpelt = Fill::Brick(BlockKind::Wood, Rgb::new(80, 47, 13), 35); + let lightpelt = Fill::Brick(BlockKind::Wood, Rgb::new(54, 25, 1), 25); + + let start = (start_wpos + 2) + .as_() + .with_z(land.get_alt_approx(start_wpos) + 0.0); + let end = (end_wpos + 2) + .as_() + .with_z(land.get_alt_approx(end_wpos) + 0.0); + let randstart = start % 10.0 - 5.; + let randend = end % 10.0 - 5.0; + let mid = (start + end) / 2.0; + + let start = start_wpos.as_().with_z(land.get_alt_approx(start_wpos)); + let end = end_wpos.as_().with_z(land.get_alt_approx(end_wpos)); + + let wall_base_height = 3.0; + let wall_mid_thickness = 2.0; + let wall_mid_height = 15.0 + wall_base_height; + + let highwall = + painter.segment_prism(start, end, wall_mid_thickness, wall_mid_height); + painter.fill(highwall, lightstone.clone()); + + painter + .segment_prism(start, end, wall_mid_thickness + 1.0, wall_mid_height - 8.0) + .fill(midstone.clone()); + let wallexterior = painter + .segment_prism(start, end, wall_mid_thickness + 2.0, wall_mid_height - 15.0) + .translate(Vec3::new(0, 0, 8)); + let wallstrut = painter + .segment_prism(start, end, wall_mid_thickness - 1.0, wall_mid_height) + .translate(Vec3::new(0, 0, 8)); + let wallexteriorlow = wallexterior.translate(Vec3::new(0, 0, -2)); + painter.fill(wallexterior, darkpelt.clone()); + painter.fill(wallexteriorlow, lightpelt.clone()); + + let exclusion = painter.line( + Vec3::new( + start.x as i32, + start.y as i32, + start.z as i32 + wall_mid_height as i32 + 2, + ), + Vec3::new( + end.x as i32, + end.y as i32, + end.z as i32 + wall_mid_height as i32 + 2, + ), + 5.0, + ); + + let top = painter + .line( + Vec3::new( + start.x as i32, + start.y as i32, + start.z as i32 + wall_mid_height as i32 + 5, + ), + Vec3::new( + end.x as i32, + end.y as i32, + end.z as i32 + wall_mid_height as i32 + 5, + ), + 4.0, + ) + .without(exclusion); + + let overtop = top.translate(Vec3::new(0, 0, 1)); + + let cyl = painter.cylinder_with_radius( + Vec3::new(start.x as i32, start.y as i32, start.z as i32), + 5.0, + wall_mid_height - 8.0, + ); + painter.fill(cyl, darkstone.clone()); + let cylsupport = painter.cylinder_with_radius( + Vec3::new(start.x as i32, start.y as i32, start.z as i32), + 5.0, + wall_mid_height + 20.0, + ); + let cylsub = painter.cylinder_with_radius( + Vec3::new(start.x as i32, start.y as i32, start.z as i32), + 4.0, + wall_mid_height + 20.0, + ); + let hollowcyl = cylsupport.without(cylsub); + let roofpoles = wallstrut.intersect(hollowcyl); + painter.fill(roofpoles, midstone.clone()); + + painter.fill(top, darkpelt.clone()); + + let startshift = + Vec3::new(randstart.x * 3.0, randstart.y * 3.0, randstart.z * 1.0); + let endshift = Vec3::new(randend.x * 3.0, randend.y * 3.0, randend.z * 1.0); + + painter + .cubic_bezier(start, mid + startshift, mid + endshift, end, 2.5) + .translate(Vec3::new(0, 0, 27)) + .intersect(overtop) + .fill(lightpelt.clone()); + + painter + .sphere_with_radius( + Vec3::new( + start.x as i32, + start.y as i32, + start.z as i32 + wall_mid_height as i32 - 8, + ), + 5.0, + ) + .without(cyl) + .clear(); + }) + } + + // Create towers + self.wall_towers.iter().for_each(|point| { + let wpos = point.xy() + self.origin; + + // Tower base + let tower_depth = 3; + let tower_base_pos = wpos.with_z(land.get_alt_approx(wpos) as i32 - tower_depth); + let tower_radius = 5.0; + let tower_height = 30.0; + + let randx = wpos.x.abs() % 10; + let randy = wpos.y.abs() % 10; + let randz = (land.get_alt_approx(wpos) as i32).abs() % 10; + //layers of rings, starting at exterior + let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12); + let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12); + let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24); + let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12); + + let twist = painter.cubic_bezier( + tower_base_pos + Vec3::new(4, 4, 8), + tower_base_pos + Vec3::new(-9, 9, 14), + tower_base_pos + Vec3::new(-11, -11, 16), + tower_base_pos + Vec3::new(4, -4, 21), + 1.5, + ); + let mosstwist = twist.translate(Vec3::new(0, 0, 1)); + let mossarea = twist.translate(Vec3::new(1, 0, 1)); + + mossarea + .intersect(mosstwist) + .without(twist) + .fill(moss.clone()); + twist.fill(darkwood.clone()); + + let outside = painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32), + tower_radius, + tower_height, + ) + .without(painter.cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32), + tower_radius - 1.0, + tower_height, + )); + outside.fill(lightwood.clone()); + painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32), + tower_radius - 1.0, + tower_height, + ) + .fill(darkwood.clone()); + painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32), + tower_radius - 2.0, + tower_height, + ) + .fill(lightwood.clone()); + painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32), + tower_radius - 3.0, + tower_height, + ) + .fill(darkwood); + painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32), + tower_radius - 4.0, + tower_height, + ) + .fill(lightwood); + //top layer, green above the tower + painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32), + tower_radius, + 2.0, + ) + .fill(moss); + //standing area one block deeper + painter + .cylinder_with_radius( + wpos.with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 9), + tower_radius - 1.0, + 1.0, + ) + .clear(); + //remove top sphere from trunk + painter + .sphere_with_radius( + Vec2::new(wpos.x - (randy - 5) / 2, wpos.y - (randz - 5) / 2).with_z( + land.get_alt_approx(wpos) as i32 + tower_height as i32 + 6 - randx / 3, + ), + 5.5, + ) + .clear(); + //remove bark from exterior layer + painter + .sphere_with_radius( + Vec2::new(wpos.x - (randy - 5) * 2, wpos.y - (randz - 5) * 2) + .with_z(land.get_alt_approx(wpos) as i32 + randx * 2), + 7.5, + ) + .intersect(outside) + .clear(); + + painter + .sphere_with_radius( + Vec2::new(wpos.x - (randx - 5) * 2, wpos.y - (randy - 5) * 2) + .with_z(land.get_alt_approx(wpos) as i32 + randz * 2), + 5.5, + ) + .intersect(outside) + .clear(); + + //cut out standing room + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 3, wpos.y - 10) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8), + max: Vec2::new(wpos.x + 3, wpos.y + 10) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 3), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 10, wpos.y - 3) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8), + max: Vec2::new(wpos.x + 10, wpos.y + 3) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 3), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 10) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8), + max: Vec2::new(wpos.x + 2, wpos.y + 10) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 2), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 10, wpos.y - 2) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 8), + max: Vec2::new(wpos.x + 10, wpos.y + 2) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 2), + }) + .clear(); + //flags + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - tower_radius as i32 - 1) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16), + max: Vec2::new(wpos.x + 2, wpos.y + tower_radius as i32 + 1) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 10), + }) + .intersect(outside) + .fill(red.clone()); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - tower_radius as i32 - 1, wpos.y - 2) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16), + max: Vec2::new(wpos.x + tower_radius as i32 + 1, wpos.y + 2) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 10), + }) + .intersect(outside) + .fill(red.clone()); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 1, wpos.y - tower_radius as i32 - 1) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 17), + max: Vec2::new(wpos.x + 1, wpos.y + tower_radius as i32 + 1) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16), + }) + .intersect(outside) + .fill(red.clone()); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - tower_radius as i32 - 1, wpos.y - 1) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 17), + max: Vec2::new(wpos.x + tower_radius as i32 + 1, wpos.y + 1) + .with_z(land.get_alt_approx(wpos) as i32 + tower_height as i32 - 16), + }) + .intersect(outside) + .fill(red); + }); + + self.structure_locations + .iter() + .for_each(|(kind, loc, door_dir)| { + let wpos = self.origin + loc.xy(); + let alt = land.get_alt_approx(wpos) as i32; + + fn generate_hut( + painter: &Painter, + wpos: Vec2, + alt: i32, + door_dir: Dir, + hut_radius: f32, + hut_wall_height: f32, + door_height: i32, + roof_height: f32, + roof_overhang: f32, + ) { + let randx = wpos.x.abs() % 10; + let randy = wpos.y.abs() % 10; + let randz = alt.abs() % 10; + let hut_wall_height = hut_wall_height + randy as f32 * 1.5; + + let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12); + let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12); + let moss = Fill::Brick(BlockKind::Leaves, Rgb::new(22, 36, 20), 24); + + // Floor + let base = wpos.with_z(alt - 4); + painter + .cylinder_with_radius(base, hut_radius + 1.0, 6.0) + .fill(darkwood.clone()); + + // Wall + let floor_pos = wpos.with_z(alt + 1); + //alternating colors for ring pattern on ceiling + painter + .cylinder_with_radius(floor_pos, hut_radius, hut_wall_height + 3.0) + .fill(lightwood.clone()); + painter + .cylinder_with_radius(floor_pos, hut_radius - 1.0, hut_wall_height + 3.0) + .fill(darkwood.clone()); + painter + .cylinder_with_radius(floor_pos, hut_radius - 2.0, hut_wall_height + 3.0) + .fill(lightwood); + painter + .cylinder_with_radius(floor_pos, hut_radius - 3.0, hut_wall_height + 3.0) + .fill(darkwood); + painter + .cylinder_with_radius(floor_pos, hut_radius - 1.0, hut_wall_height) + .clear(); + + // Door + let aabb_min = |dir| { + match dir { + Dir::X | Dir::Y => wpos - Vec2::one(), + Dir::NegX | Dir::NegY => wpos + randx / 5 + 1, + } + .with_z(alt + 1) + }; + let aabb_max = |dir| { + (match dir { + Dir::X | Dir::Y => wpos + randx / 5 + 1, + Dir::NegX | Dir::NegY => wpos - Vec2::one(), + } + dir.to_vec2() * hut_radius as i32) + .with_z(alt + 1 + door_height) + }; + + painter + .aabb(Aabb { + min: aabb_min(door_dir), + max: aabb_max(door_dir), + }) + .clear(); + + // Roof + let roof_radius = hut_radius + roof_overhang; + painter + .cone_with_radius( + wpos.with_z(alt + 3 + hut_wall_height as i32), + roof_radius - 1.0, + roof_height - 1.0, + ) + .fill(moss.clone()); + + //small bits hanging from huts + let tendril1 = painter.line( + Vec2::new(wpos.x - 3, wpos.y - 5) + .with_z(alt + (hut_wall_height * 0.75) as i32), + Vec2::new(wpos.x - 3, wpos.y - 5).with_z(alt + 3 + hut_wall_height as i32), + 1.0, + ); + + let tendril2 = painter.line( + Vec2::new(wpos.x + 4, wpos.y + 2) + .with_z(alt + 1 + (hut_wall_height * 0.75) as i32), + Vec2::new(wpos.x + 4, wpos.y + 2).with_z(alt + 3 + hut_wall_height as i32), + 1.0, + ); + + let tendril3 = tendril2.translate(Vec3::new(-7, 2, 0)); + let tendril4 = tendril1.translate(Vec3::new(7, 4, 0)); + let tendrils = tendril1.union(tendril2).union(tendril3).union(tendril4); + + tendrils.fill(moss); + + //sphere to delete some hut + painter + .sphere_with_radius( + Vec2::new(wpos.x - (randy - 5) / 2, wpos.y - (randz - 5) / 2) + .with_z(alt + 12 + hut_wall_height as i32 - randx / 3), + 8.5, + ) + .clear(); + } + + fn generate_chieftainhut( + painter: &Painter, + wpos: Vec2, + alt: i32, + roof_height: f32, + ) { + let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12); + let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12); + let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24); + let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12); + + // Floor + let raise = 5; + + let platform = painter.aabb(Aabb { + min: (wpos - 20).with_z(alt + raise), + max: (wpos + 20).with_z(alt + raise + 1), + }); + + painter.fill(platform, darkwood.clone()); + + let supports = painter + .line( + (wpos - 19).with_z(alt - 3), + (wpos - 19).with_z(alt + raise), + 2.0, + ) + .repeat(Vec3::new(37, 0, 0), 1) + .repeat(Vec3::new(0, 37, 0), 1); + + let supports_inner = painter + .aabb(Aabb { + min: (wpos - 19).with_z(alt - 10) + Vec3::unit_y() * 17, + max: (wpos - 15).with_z(alt + raise) + Vec3::unit_y() * 17, + }) + .repeat(Vec3::new(17, 17, 0), 1) + .repeat(Vec3::new(17, -17, 0), 1); + // let support_inner_2 = support_inner_1.translate(Vec3::new(34, 0, 0)); + // let support_inner_3 = support_inner_1.translate(Vec3::new(17, 17, 0)); + // let support_inner_4 = support_inner_1.translate(Vec3::new(17, -17, 0)); + let supports = supports.union(supports_inner); + + painter.fill(supports, darkwood.clone()); + let height_1 = 10.0; + let height_2 = 12.0; + let rad_1 = 18.0; + let rad_2 = 15.0; + + let floor_pos = wpos.with_z(alt + 1 + raise); + painter + .cylinder_with_radius(floor_pos, rad_1, height_1) + .fill(lightwood.clone()); + painter + .cylinder_with_radius(floor_pos, rad_1 - 1.0, height_1) + .clear(); + + let floor2_pos = wpos.with_z(alt + 1 + raise + height_1 as i32); + painter + .cylinder_with_radius(floor2_pos, rad_2, height_2) + .fill(lightwood); + + // Roof + let roof_radius = rad_1 + 5.0; + let roof1 = painter.cone_with_radius( + wpos.with_z(alt + 1 + height_1 as i32 + raise), + roof_radius, + roof_height, + ); + roof1.fill(moss.clone()); + let roof_radius = rad_2 + 1.0; + painter + .cone_with_radius( + wpos.with_z(alt + 1 + height_1 as i32 + height_2 as i32 + raise), + roof_radius, + roof_height, + ) + .fill(moss); + let centerspot = painter.line( + (wpos + 1).with_z(alt + raise + height_1 as i32 + 2), + (wpos + 1).with_z(alt + raise + height_1 as i32 + 2), + 1.0, + ); + let roof_support_1 = painter.line( + (wpos + rad_1 as i32 - 7).with_z(alt + raise + height_1 as i32 - 2), + (wpos + rad_1 as i32 - 2).with_z(alt + raise + height_1 as i32), + 1.5, + ); + let roof_strut = painter.line( + (wpos + rad_1 as i32 - 7).with_z(alt + raise + height_1 as i32 + 2), + (wpos + rad_1 as i32 - 2).with_z(alt + raise + height_1 as i32 + 2), + 1.0, + ); + let wall2support = painter.line( + (wpos + rad_2 as i32 - 5).with_z(alt + raise + height_1 as i32 + 7), + (wpos + rad_2 as i32 - 5).with_z(alt + raise + height_1 as i32 + 12), + 1.5, + ); + let wall2roof = painter.line( + (wpos + rad_2 as i32 - 7).with_z(alt + raise + height_2 as i32 + 12), + (wpos + rad_2 as i32 - 4).with_z(alt + raise + height_2 as i32 + 10), + 2.0, + ); + + let roof_support_1 = centerspot + .union(roof_support_1) + .union(roof_strut) + .union(wall2support) + .union(wall2roof); + + let roof_support_2 = + roof_support_1.rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1)); + let roof_support_3 = + roof_support_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1)); + let roof_support_4 = + roof_support_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)); + let roof_support = roof_support_1 + .union(roof_support_2) + .union(roof_support_3) + .union(roof_support_4); + + painter.fill(roof_support, red.clone()); + + let spike_high = painter.cubic_bezier( + (wpos + rad_2 as i32 - 5).with_z(alt + raise + height_1 as i32 + 2), + (wpos + rad_2 as i32 - 2).with_z(alt + raise + height_1 as i32 + 2), + (wpos + rad_2 as i32 - 1).with_z(alt + raise + height_1 as i32 + 5), + (wpos + rad_2 as i32).with_z(alt + raise + height_1 as i32 + 8), + 1.3, + ); + let spike_low = + spike_high.rotate_about_min(Mat3::new(1, 0, 0, 0, 1, 0, 0, 0, -1)); + let spike_1 = centerspot.union(spike_low).union(spike_high); + + let spike_2 = spike_1.rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1)); + let spike_3 = spike_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1)); + let spike_4 = spike_1.rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)); + let spikes = spike_1.union(spike_2).union(spike_3).union(spike_4); + + painter.fill( + spikes, + Fill::Brick(BlockKind::Wood, Rgb::new(112, 110, 99), 24), + ); + //Open doorways (top floor) + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 15) + .with_z(alt + raise + height_1 as i32 + 3), + max: Vec2::new(wpos.x + 2, wpos.y + 15) + .with_z(alt + raise + height_1 as i32 + height_2 as i32), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 3, wpos.y - 15) + .with_z(alt + raise + height_1 as i32 + 4), + max: Vec2::new(wpos.x + 3, wpos.y + 15) + .with_z(alt + raise + height_1 as i32 + 10), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 15, wpos.y - 2) + .with_z(alt + raise + height_1 as i32 + 3), + max: Vec2::new(wpos.x + 15, wpos.y + 2) + .with_z(alt + raise + height_1 as i32 + height_2 as i32), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 15, wpos.y - 3) + .with_z(alt + raise + height_1 as i32 + 4), + max: Vec2::new(wpos.x + 15, wpos.y + 3) + .with_z(alt + raise + height_1 as i32 + 10), + }) + .clear(); + + // + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 18, wpos.y - 2) + .with_z(alt + raise + height_1 as i32 - 9), + max: Vec2::new(wpos.x + 18, wpos.y + 2) + .with_z(alt + raise + height_1 as i32 - 1), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 18) + .with_z(alt + raise + height_1 as i32 - 9), + max: Vec2::new(wpos.x + 2, wpos.y + 18) + .with_z(alt + raise + height_1 as i32 - 1), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 18, wpos.y - 3) + .with_z(alt + raise + height_1 as i32 - 9), + max: Vec2::new(wpos.x + 18, wpos.y + 3) + .with_z(alt + raise + height_1 as i32 - 3), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 3, wpos.y - 18) + .with_z(alt + raise + height_1 as i32 - 9), + max: Vec2::new(wpos.x + 3, wpos.y + 18) + .with_z(alt + raise + height_1 as i32 - 3), + }) + .clear(); + //Roofing details + + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 23, wpos.y - 2) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 23, wpos.y + 2) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .translate(Vec3::new(0, 0, -1)) + .fill(darkwood.clone()); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 23, wpos.y - 2) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 23, wpos.y + 2) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .fill(red.clone()); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 23) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 2, wpos.y + 23) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .translate(Vec3::new(0, 0, -1)) + .fill(darkwood); + painter + .aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 23) + .with_z(alt + raise + height_1 as i32 - 3), + max: Vec2::new(wpos.x + 2, wpos.y + 23) + .with_z(alt + raise + height_1 as i32 + 7), + }) + .intersect(roof1) + .fill(red); + painter + .cylinder_with_radius(floor2_pos, rad_2 - 1.0, height_2) + .clear(); + } + + match kind { + AdletStructure::Hut => { + let hut_radius = 5.0; + let hut_wall_height = 4.0; + let door_height = 4; + let roof_height = 3.0; + let roof_overhang = 1.0; + + generate_hut( + painter, + wpos, + alt, + *door_dir, + hut_radius, + hut_wall_height, + door_height, + roof_height, + roof_overhang, + ); + }, + AdletStructure::VeloriteHut => { + let rand = Vec3::new( + wpos.x.abs() % 10, + wpos.y.abs() % 10, + (land.get_alt_approx(wpos) as i32).abs() % 10, + ); + + let length = 14; + let width = 6; + let height = alt + 12; + let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12); + let lightwood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12); + let moss = Fill::Brick(BlockKind::Wood, Rgb::new(22, 36, 20), 24); + let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12); + + let low1 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - width, wpos.y - length).with_z(height + 1), + max: Vec2::new(wpos.x + width, wpos.y + length).with_z(height + 2), + }); + + let low2 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - length, wpos.y - width).with_z(height + 1), + max: Vec2::new(wpos.x + length, wpos.y + width).with_z(height + 2), + }); + let top1 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - width + 1, wpos.y - length + 1) + .with_z(height + 2), + max: Vec2::new(wpos.x + width - 1, wpos.y + length - 1) + .with_z(height + 2 + 1), + }); + + let top2 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - length + 1, wpos.y - width + 1) + .with_z(height + 2), + max: Vec2::new(wpos.x + length - 1, wpos.y + width - 1) + .with_z(height + 2 + 1), + }); + let low = low1.union(low2); + let top = top1.union(top2); + + let roof = low1.union(low2).union(top1).union(top2); + let roofmoss = roof.translate(Vec3::new(0, 0, 1)).without(top).without(low); + top.fill(darkwood.clone()); + low.fill(lightwood); + roofmoss.fill(moss); + painter + .sphere_with_radius( + Vec2::new(wpos.x + rand.y - 5, wpos.y + rand.z - 5) + .with_z(height + 4), + 7.0, + ) + .intersect(roofmoss) + .clear(); + painter + .sphere_with_radius( + Vec2::new(wpos.x + rand.x - 5, wpos.y + rand.y - 5) + .with_z(height + 4), + 4.0, + ) + .intersect(roofmoss) + .clear(); + painter + .sphere_with_radius( + Vec2::new(wpos.x + 2 * (rand.z - 5), wpos.y + 2 * (rand.x - 5)) + .with_z(height + 4), + 4.0, + ) + .intersect(roofmoss) + .clear(); + + //inside leg + let leg1 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - width, wpos.y - width).with_z(height - 12), + max: Vec2::new(wpos.x - width + 2, wpos.y - width + 2) + .with_z(height + 2), + }); + let legsupport1 = painter.line( + Vec2::new(wpos.x - width, wpos.y - width).with_z(height - 6), + Vec2::new(wpos.x - width + 3, wpos.y - width + 3).with_z(height), + 1.0, + ); + + let leg2 = leg1.translate(Vec3::new(0, width * 2 - 2, 0)); + let leg3 = leg1.translate(Vec3::new(width * 2 - 2, 0, 0)); + let leg4 = leg1.translate(Vec3::new(width * 2 - 2, width * 2 - 2, 0)); + let legsupport2 = legsupport1 + .rotate_about_min(Mat3::new(0, 1, 0, -1, 0, 0, 0, 0, 1)) + .translate(Vec3::new(1, width * 2 + 1, 0)); + let legsupport3 = legsupport1 + .rotate_about_min(Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1)) + .translate(Vec3::new(width * 2 + 1, 1, 0)); + let legsupport4 = legsupport1 + .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)) + .translate(Vec3::new(width * 2 + 2, width * 2 + 2, 0)); + + let legsupports = legsupport1 + .union(legsupport2) + .union(legsupport3) + .union(legsupport4); + + let legs = leg1.union(leg2).union(leg3).union(leg4); + legs.fill(darkwood); + legsupports.without(legs).fill(red); + + let spike1 = painter.line( + Vec2::new(wpos.x - 12, wpos.y + 2).with_z(height + 3), + Vec2::new(wpos.x - 7, wpos.y + 2).with_z(height + 5), + 1.0, + ); + let spike2 = painter.line( + Vec2::new(wpos.x - 12, wpos.y - 3).with_z(height + 3), + Vec2::new(wpos.x - 7, wpos.y - 3).with_z(height + 5), + 1.0, + ); + let spikes = spike1.union(spike2); + let spikesalt = spikes + .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)) + .translate(Vec3::new(26, 8, 0)); + let spikeshalf = spikes.union(spikesalt); + let spikesotherhalf = spikeshalf + .rotate_about_min(Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1)) + .translate(Vec3::new(16, -9, 0)); + let spikesall = spikeshalf.union(spikesotherhalf); + + spikesall.fill(Fill::Brick(BlockKind::Wood, Rgb::new(112, 110, 99), 24)); + let crystal1 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 3).with_z(alt), + max: Vec2::new(wpos.x + 2, wpos.y + 1).with_z(alt + 7), + }); + let crystal2 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - 3, wpos.y - 3).with_z(alt), + max: Vec2::new(wpos.x + 3, wpos.y + 1).with_z(alt + 6), + }); + let crystal3 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - 2, wpos.y - 2).with_z(alt), + max: Vec2::new(wpos.x + 4, wpos.y + 3).with_z(alt + 4), + }); + let crystal4 = painter.aabb(Aabb { + min: Vec2::new(wpos.x - 1, wpos.y - 4).with_z(alt), + max: Vec2::new(wpos.x + 2, wpos.y + 2).with_z(alt + 8), + }); + let crystalp1 = crystal1.union(crystal3); + let crystalp2 = crystal2.union(crystal4); + + crystalp1.fill(Fill::Brick( + BlockKind::GlowingRock, + Rgb::new(50, 225, 210), + 24, + )); + crystalp2.fill(Fill::Brick( + BlockKind::GlowingRock, + Rgb::new(36, 187, 151), + 24, + )); + }, + AdletStructure::Igloo => { + let igloo_pos = wpos.with_z(alt); + + lazy_static! { + pub static ref IGLOO: AssetHandle = + PrefabStructure::load_group("site_structures.adlet.igloo"); + } + + let igloo = IGLOO.read(); + let igloo = igloo[self.seed as usize % igloo.len()].clone(); + + painter + .prim(Primitive::Prefab(Box::new(igloo.clone()))) + .translate(igloo_pos) + .fill(Fill::Prefab(Box::new(igloo), igloo_pos, self.seed)); + }, + AdletStructure::ChieftainHut => { + let roof_height = 3.0; + + generate_chieftainhut(painter, wpos, alt, roof_height); + }, + + AdletStructure::Banner => { + let rand = Vec3::new( + wpos.x.abs() % 10, + wpos.y.abs() % 10, + (land.get_alt_approx(wpos) as i32).abs() % 10, + ); + + let darkwood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 12); + let moss = Fill::Brick(BlockKind::Leaves, Rgb::new(22, 36, 20), 24); + let red = Fill::Brick(BlockKind::Wood, Rgb::new(102, 31, 2), 12); + let flag = painter.aabb(Aabb { + min: Vec2::new(wpos.x + 1, wpos.y - 1).with_z(alt + 8), + max: Vec2::new(wpos.x + 8, wpos.y).with_z(alt + 38), + }); + flag.fill(red); + //add green streaks + let streak1 = painter + .line( + Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 20), + Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 33), + 4.0, + ) + .intersect(flag); + + let streak2 = painter + .line( + Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 12), + Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 25), + 1.5, + ) + .intersect(flag); + let streak3 = painter + .line( + Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 8), + Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 21), + 1.0, + ) + .intersect(flag); + let streaks = streak1.union(streak2).union(streak3); + streaks.fill(moss); + //erase from top and bottom of rectangle flag for shape + painter + .line( + Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 31), + Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 44), + 5.0, + ) + .intersect(flag) + .clear(); + painter + .sphere_with_radius(Vec2::new(wpos.x + 8, wpos.y).with_z(alt + 8), 6.0) + .intersect(flag) + .clear(); + //tatters + painter + .line( + Vec2::new(wpos.x + 3 + rand.x / 5, wpos.y - 1) + .with_z(alt + 15 + rand.y), + Vec2::new(wpos.x + 3 + rand.y / 5, wpos.y - 1).with_z(alt + 5), + 0.9 * rand.z as f32 / 4.0, + ) + .clear(); + painter + .line( + Vec2::new(wpos.x + 4 + rand.z / 2, wpos.y - 1) + .with_z(alt + 20 + rand.x), + Vec2::new(wpos.x + 4 + rand.z / 2, wpos.y - 1) + .with_z(alt + 17 + rand.y), + 0.9 * rand.z as f32 / 6.0, + ) + .clear(); + + //flagpole + let column = painter.aabb(Aabb { + min: (wpos - 1).with_z(alt - 3), + max: (wpos).with_z(alt + 30), + }); + + let arm = painter.line( + Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt + 26), + Vec2::new(wpos.x + 8, wpos.y - 1).with_z(alt + 39), + 0.9, + ); + let flagpole = column.union(arm); + flagpole.fill(darkwood); + }, + AdletStructure::WatchTower => { + let platform_1_height = 14; + let platform_2_height = 20; + let platform_3_height = 26; + let roof_height = 30; + + let platform_1 = painter.aabb(Aabb { + min: (wpos + 1).with_z(alt + platform_1_height), + max: (wpos + 10).with_z(alt + platform_1_height + 1), + }); + + let platform_2 = painter.aabb(Aabb { + min: (wpos + 1).with_z(alt + platform_2_height), + max: (wpos + 10).with_z(alt + platform_2_height + 1), + }); + + let platform_3 = painter.aabb(Aabb { + min: (wpos + 2).with_z(alt + platform_3_height), + max: (wpos + 9).with_z(alt + platform_3_height + 1), + }); + + let support_1 = painter.line( + wpos.with_z(alt), + (wpos + 2).with_z(alt + platform_1_height), + 1.0, + ); + let support_2 = support_1 + .rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1)) + .translate(Vec3::new(0, 13, 0)); + let support_3 = support_1 + .rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1)) + .translate(Vec3::new(13, 0, 0)); + let support_4 = support_1 + .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)) + .translate(Vec3::new(13, 13, 0)); + + let supports = support_1.union(support_2).union(support_3).union(support_4); + + let platform_1_supports = painter + .plane( + Aabr { + min: (wpos + 2), + max: (wpos + 9), + }, + (wpos + 3).with_z(alt + platform_1_height + 1), + Vec2::new(0.0, 1.0), + ) + .union(painter.plane( + Aabr { + min: (wpos + 2), + max: (wpos + 9), + }, + (wpos + 3).with_z(alt + platform_2_height), + Vec2::new(0.0, -1.0), + )) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 2) + .with_z(alt + platform_1_height), + max: Vec2::new(wpos.x + 8, wpos.y + 9) + .with_z(alt + platform_2_height), + }), + ) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 2, wpos.y + 2) + .with_z(alt + platform_2_height), + max: Vec2::new(wpos.x + 9, wpos.y + 9) + .with_z(alt + platform_2_height + 2), + }), + ) + .union( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 2, wpos.y + 2) + .with_z(alt + platform_1_height), + max: Vec2::new(wpos.x + 9, wpos.y + 9) + .with_z(alt + platform_2_height), + }), + ) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 2) + .with_z(alt + platform_1_height), + max: Vec2::new(wpos.x + 8, wpos.y + 9) + .with_z(alt + platform_2_height), + }), + ) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 2, wpos.y + 3) + .with_z(alt + platform_1_height), + max: Vec2::new(wpos.x + 9, wpos.y + 8) + .with_z(alt + platform_2_height), + }), + ); + + let platform_2_supports = painter + .plane( + Aabr { + min: (wpos + 3), + max: (wpos + 8), + }, + (wpos + 3).with_z(alt + platform_2_height + 1), + Vec2::new(1.0, 0.0), + ) + .union(painter.plane( + Aabr { + min: (wpos + 3), + max: (wpos + 8), + }, + (wpos + 3).with_z(alt + platform_3_height), + Vec2::new(-1.0, 0.0), + )) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 4) + .with_z(alt + platform_2_height), + max: Vec2::new(wpos.x + 8, wpos.y + 7) + .with_z(alt + platform_3_height), + }), + ) + .union( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 3) + .with_z(alt + platform_2_height), + max: Vec2::new(wpos.x + 8, wpos.y + 8) + .with_z(alt + platform_3_height), + }), + ) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 4, wpos.y + 3) + .with_z(alt + platform_2_height), + max: Vec2::new(wpos.x + 7, wpos.y + 8) + .with_z(alt + platform_3_height), + }), + ) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 4) + .with_z(alt + platform_2_height), + max: Vec2::new(wpos.x + 8, wpos.y + 7) + .with_z(alt + platform_3_height), + }), + ); + + let roof = painter + .gable( + Aabb { + min: (wpos + 2).with_z(alt + roof_height), + max: (wpos + 9).with_z(alt + roof_height + 4), + }, + 0, + Dir::Y, + ) + .without( + painter.gable( + Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 2) + .with_z(alt + roof_height), + max: Vec2::new(wpos.x + 8, wpos.y + 9) + .with_z(alt + roof_height + 3), + }, + 0, + Dir::Y, + ), + ); + + let roof_pillars = painter + .aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 3) + .with_z(alt + platform_3_height), + max: Vec2::new(wpos.x + 8, wpos.y + 8) + .with_z(alt + roof_height + 1), + }) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 4, wpos.y + 3) + .with_z(alt + platform_3_height), + max: Vec2::new(wpos.x + 7, wpos.y + 8) + .with_z(alt + roof_height), + }), + ) + .without( + painter.aabb(Aabb { + min: Vec2::new(wpos.x + 3, wpos.y + 4) + .with_z(alt + platform_3_height), + max: Vec2::new(wpos.x + 8, wpos.y + 7) + .with_z(alt + roof_height), + }), + ); + //skirt + let skirt1 = painter + .aabb(Aabb { + min: Vec2::new(wpos.x + 1, wpos.y) + .with_z(alt + platform_1_height - 3), + max: Vec2::new(wpos.x + 4, wpos.y + 1) + .with_z(alt + platform_1_height + 1), + }) + .without(painter.line( + Vec2::new(wpos.x + 1, wpos.y).with_z(alt + platform_1_height - 1), + Vec2::new(wpos.x + 1, wpos.y).with_z(alt + platform_1_height - 3), + 1.0, + )) + .without(painter.line( + Vec2::new(wpos.x + 3, wpos.y).with_z(alt + platform_1_height - 2), + Vec2::new(wpos.x + 3, wpos.y).with_z(alt + platform_1_height - 3), + 1.0, + )); + let skirt2 = skirt1 + .translate(Vec3::new(6, 0, 0)) + .rotate_about_min(Mat3::new(-1, 0, 0, 0, 1, 0, 0, 0, 1)); + let skirt3 = skirt2.translate(Vec3::new(3, 0, 0)); + + let skirtside1 = skirt1.union(skirt2).union(skirt3); + let skirtside2 = skirtside1 + .rotate_about_min(Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1)) + .translate(Vec3::new(-1, 2, 0)); + + let skirtcorner1 = skirtside1.union(skirtside2); + let skirtcorner2 = skirtcorner1 + .rotate_about_min(Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1)) + .translate(Vec3::new(13, 11, 0)); + + let skirt1 = skirtcorner1.union(skirtcorner2); + let skirt2 = skirt1 + .rotate_about_min(Mat3::new(1, 0, 0, 0, -1, 0, 0, 0, 1)) + .translate(Vec3::new(0, 11, 6)); + + let skirt = skirt1.union(skirt2).union(roof); + painter.fill( + skirt, + Fill::Brick(BlockKind::Leaves, Rgb::new(22, 36, 20), 24), + ); + + let towerplatform = platform_1.union(platform_2).union(platform_3); + + painter.fill( + towerplatform, + Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 24), + ); + let towervertical = supports + .union(platform_1_supports) + .union(platform_2_supports) + .union(roof_pillars); + + painter.fill( + towervertical, + Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 24), + ); + }, + } + }); + + // Create tunnels beneath the fortification + let wood = Fill::Brick(BlockKind::Wood, Rgb::new(55, 25, 8), 24); + let dirt = Fill::Brick(BlockKind::Earth, Rgb::new(55, 25, 8), 24); + let alt = land.get_alt_approx(self.origin) as i32; + let stump = painter + .cylinder(Aabb { + min: (self.tunnels.start.xy() - 10).with_z(alt - 15), + max: (self.tunnels.start.xy() + 11).with_z(alt + 10), + }) + .union(painter.cylinder(Aabb { + min: (self.tunnels.start.xy() - 11).with_z(alt), + max: (self.tunnels.start.xy() + 12).with_z(alt + 2), + })) + .union(painter.line( + self.tunnels.start.xy().with_z(alt + 10), + (self.tunnels.start.xy() + 15).with_z(alt - 8), + 5.0, + )) + .union(painter.line( + self.tunnels.start.xy().with_z(alt + 10), + Vec2::new(self.tunnels.start.x - 15, self.tunnels.start.y + 15).with_z(alt - 8), + 5.0, + )) + .union(painter.line( + self.tunnels.start.xy().with_z(alt + 10), + Vec2::new(self.tunnels.start.x + 15, self.tunnels.start.y - 15).with_z(alt - 8), + 5.0, + )) + .union(painter.line( + self.tunnels.start.xy().with_z(alt + 10), + (self.tunnels.start.xy() - 15).with_z(alt - 8), + 5.0, + )) + .without( + painter.sphere_with_radius((self.tunnels.start.xy() + 10).with_z(alt + 26), 18.0), + ) + .without( + painter.sphere_with_radius((self.tunnels.start.xy() - 10).with_z(alt + 26), 18.0), + ); + let entrance_hollow = painter.line( + self.tunnels.start, + self.tunnels.start.xy().with_z(alt + 10), + 9.0, + ); + + let boss_room = painter.ellipsoid(Aabb { + min: (self.tunnels.end.xy() - 30).with_z(self.tunnels.end.z - 10), + max: (self.tunnels.end.xy() + 30).with_z(self.tunnels.end.z + 10), + }); + + let boss_room_clear = painter.ellipsoid(Aabb { + min: (self.tunnels.end.xy() - 29).with_z(self.tunnels.end.z - 9), + max: (self.tunnels.end.xy() + 29).with_z(self.tunnels.end.z + 9), + }); + + let random_field = RandomField::new(self.seed); + + let mut tunnels = Vec::new(); + let mut path_tunnels = Vec::new(); + let mut tunnels_clear = Vec::new(); + let mut ferns = Vec::new(); + let mut velorite_ores = Vec::new(); + let mut fire_bowls = Vec::new(); + for branch in self.tunnels.branches.iter() { + let tunnel_radius_i32 = 4 + branch.0.x % 4; + let in_path = + self.tunnels.path.contains(&branch.0) && self.tunnels.path.contains(&branch.1); + let tunnel_radius = tunnel_radius_i32 as f32; + let start = branch.0; + let end = branch.1; + let ctrl0_offset = start.x % 6 * if start.y % 2 == 0 { -1 } else { 1 }; + let ctrl1_offset = end.x % 6 * if end.y % 2 == 0 { -1 } else { 1 }; + let ctrl0 = (((start + end) / 2) + start) / 2 + ctrl0_offset; + let ctrl1 = (((start + end) / 2) + end) / 2 + ctrl1_offset; + let tunnel = painter.cubic_bezier(start, ctrl0, ctrl1, end, tunnel_radius); + let tunnel_clear = painter.cubic_bezier(start, ctrl0, ctrl1, end, tunnel_radius - 1.0); + let mut fern_scatter = painter.empty(); + let mut velorite_scatter = painter.empty(); + let mut fire_bowl_scatter = painter.empty(); + + let min_z = branch.0.z.min(branch.1.z); + let max_z = branch.0.z.max(branch.1.z); + for i in branch.0.x - tunnel_radius_i32..branch.1.x + tunnel_radius_i32 { + for j in branch.0.y - tunnel_radius_i32..branch.1.y + tunnel_radius_i32 { + if random_field.get(Vec3::new(i, j, min_z)) % 6 == 0 { + fern_scatter = fern_scatter.union(painter.aabb(Aabb { + min: Vec3::new(i, j, min_z), + max: Vec3::new(i + 1, j + 1, max_z), + })); + } + if random_field.get(Vec3::new(i, j, min_z)) % 30 == 0 { + velorite_scatter = velorite_scatter.union(painter.aabb(Aabb { + min: Vec3::new(i, j, min_z), + max: Vec3::new(i + 1, j + 1, max_z), + })); + } + if random_field.get(Vec3::new(i, j, min_z)) % 30 == 0 { + fire_bowl_scatter = fire_bowl_scatter.union(painter.aabb(Aabb { + min: Vec3::new(i, j, min_z), + max: Vec3::new(i + 1, j + 1, max_z), + })); + } + } + } + let fern = tunnel_clear.intersect(fern_scatter); + let velorite = tunnel_clear.intersect(velorite_scatter); + let fire_bowl = tunnel_clear.intersect(fire_bowl_scatter); + if in_path { + path_tunnels.push(tunnel); + } else { + tunnels.push(tunnel); + } + tunnels_clear.push(tunnel_clear); + ferns.push(fern); + velorite_ores.push(velorite); + fire_bowls.push(fire_bowl); + } + + let mut rooms = Vec::new(); + let mut rooms_clear = Vec::new(); + let mut chests_ori_0 = Vec::new(); + let mut chests_ori_2 = Vec::new(); + let mut chests_ori_4 = Vec::new(); + for terminal in self.tunnels.terminals.iter() { + let room = painter.sphere(Aabb { + min: terminal - 8, + max: terminal + 8 + 1, + }); + let room_clear = painter.sphere(Aabb { + min: terminal - 7, + max: terminal + 7 + 1, + }); + rooms.push(room); + rooms_clear.push(room_clear); + + // FIRE!!!!! + let fire_bowl = painter.aabb(Aabb { + min: terminal.with_z(terminal.z - 7), + max: terminal.with_z(terminal.z - 7) + 1, + }); + fire_bowls.push(fire_bowl); + + // Chest + let chest_seed = random_field.get(*terminal) % 5; + if chest_seed < 4 { + let chest_pos = Vec3::new(terminal.x, terminal.y - 4, terminal.z - 6); + let chest = painter.aabb(Aabb { + min: chest_pos, + max: chest_pos + 1, + }); + chests_ori_4.push(chest); + if chest_seed < 2 { + let chest_pos = Vec3::new(terminal.x, terminal.y + 4, terminal.z - 6); + let chest = painter.aabb(Aabb { + min: chest_pos, + max: chest_pos + 1, + }); + chests_ori_0.push(chest); + if chest_seed < 1 { + let chest_pos = Vec3::new(terminal.x - 4, terminal.y, terminal.z - 6); + let chest = painter.aabb(Aabb { + min: chest_pos, + max: chest_pos + 1, + }); + chests_ori_2.push(chest); + } + } + } + } + tunnels + .into_iter() + .chain(rooms.into_iter()) + .chain(core::iter::once(boss_room)) + .chain(core::iter::once(stump)) + .for_each(|prim| prim.fill(wood.clone())); + path_tunnels.into_iter().for_each(|t| t.fill(dirt.clone())); + + // Clear out insides after filling the walls in + let mut sprite_clear = Vec::new(); + tunnels_clear + .into_iter() + .chain(rooms_clear.into_iter()) + .chain(core::iter::once(boss_room_clear)) + .for_each(|prim| { + sprite_clear.push(prim.translate(Vec3::new(0, 0, 1)).intersect(prim)); + + prim.clear(); + }); + + // Place sprites + ferns + .into_iter() + .for_each(|prim| prim.fill(Fill::Block(Block::air(SpriteKind::JungleFern)))); + velorite_ores + .into_iter() + .for_each(|prim| prim.fill(Fill::Block(Block::air(SpriteKind::Velorite)))); + fire_bowls + .into_iter() + .for_each(|prim| prim.fill(Fill::Block(Block::air(SpriteKind::FireBowlGround)))); + + chests_ori_0.into_iter().for_each(|prim| { + prim.fill(Fill::Block( + Block::air(SpriteKind::DungeonChest0).with_ori(0).unwrap(), + )) + }); + chests_ori_2.into_iter().for_each(|prim| { + prim.fill(Fill::Block( + Block::air(SpriteKind::DungeonChest0).with_ori(2).unwrap(), + )) + }); + chests_ori_4.into_iter().for_each(|prim| { + prim.fill(Fill::Block( + Block::air(SpriteKind::DungeonChest0).with_ori(4).unwrap(), + )) + }); + + entrance_hollow.clear(); + sprite_clear.into_iter().for_each(|prim| prim.clear()); + } +} + +fn gnarling_mugger(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.mugger", rng) +} + +fn gnarling_stalker(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.stalker", rng) +} + +fn gnarling_logger(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.logger", rng) +} + +fn random_gnarling(pos: Vec3, rng: &mut R) -> EntityInfo { + match rng.gen_range(0..4) { + 0 => gnarling_stalker(pos, rng), + 1 => gnarling_mugger(pos, rng), + _ => gnarling_logger(pos, rng), + } +} + +fn gnarling_chieftain(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.chieftain", rng) + .with_no_flee() +} + +fn deadwood(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.wild.aggressive.deadwood", rng) +} + +fn mandragora(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.mandragora", rng) +} + +fn wood_golem(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.woodgolem", rng) +} + +fn harvester_boss(pos: Vec3, rng: &mut R) -> EntityInfo { + EntityInfo::at(pos.map(|x| x as f32)) + .with_asset_expect("common.entity.dungeon.gnarling.harvester", rng) +} + +#[derive(Default)] +struct Tunnels { + start: Vec3, + end: Vec3, + branches: Vec<(Vec3, Vec3)>, + path: Vec>, + terminals: Vec>, +} + +impl Tunnels { + /// Attempts to find a path from a `start` to the `end` using a rapidly + /// exploring random tree (RRT). A point is sampled from an AABB extending + /// slightly beyond the start and end in the x and y axes and below the + /// start in the z axis to slightly below the end. Nodes are stored in a + /// k-d tree for quicker nearest node calculations. A maximum of 7000 + /// points are sampled until the tree connects the start to the end. A + /// final path is then reconstructed from the nodes. Returns a `Tunnels` + /// struct of the RRT branches, the complete path, and the location of + /// dead ends. Each branch is a tuple of the start and end locations of + /// each segment. The path is a vector of all the nodes along the + /// complete path from the `start` to the `end`. + fn new( + start: Vec3, + end: Vec3, + is_valid_edge: F, + radius_range: (f32, f32), + rng: &mut impl Rng, + ) -> Option + where + F: Fn(Vec3, Vec3) -> bool, + { + let mut nodes = Vec::new(); + let mut node_index: usize = 0; + + // HashMap + let mut parents = HashMap::new(); + + let mut kdtree = KdTree::new(); + let startf = start.map(|a| (a + 1) as f32); + let endf = end.map(|a| (a + 1) as f32); + + let min = Vec3::new( + startf.x.min(endf.x), + startf.y.min(endf.y), + startf.z.min(endf.z), + ); + let max = Vec3::new( + startf.x.max(endf.x), + startf.y.max(endf.y), + startf.z.max(endf.z), + ); + + kdtree + .add(&[startf.x, startf.y, startf.z], node_index) + .ok()?; + nodes.push(startf); + node_index += 1; + let mut connect = false; + + for _i in 0..7000 { + let radius: f32 = rng.gen_range(radius_range.0..radius_range.1); + let radius_sqrd = radius.powi(2); + if connect { + break; + } + let sampled_point = Vec3::new( + rng.gen_range(min.x - 20.0..max.x + 20.0), + rng.gen_range(min.y - 20.0..max.y + 20.0), + rng.gen_range(min.z - 20.0..max.z - 7.0), + ); + let nearest_index = *kdtree + .nearest_one( + &[sampled_point.x, sampled_point.y, sampled_point.z], + &squared_euclidean, + ) + .ok()? + .1 as usize; + let nearest = nodes[nearest_index]; + let dist_sqrd = sampled_point.distance_squared(nearest); + let new_point = if dist_sqrd > radius_sqrd { + nearest + (sampled_point - nearest).normalized().map(|a| a * radius) + } else { + sampled_point + }; + if is_valid_edge( + nearest.map(|e| e.floor() as i32), + new_point.map(|e| e.floor() as i32), + ) { + kdtree + .add(&[new_point.x, new_point.y, new_point.z], node_index) + .ok()?; + nodes.push(new_point); + parents.insert(node_index, nearest_index); + node_index += 1; + } + if new_point.distance_squared(endf) < radius.powi(2) { + connect = true; + } + } + + let mut path = Vec::new(); + let nearest_index = *kdtree + .nearest_one(&[endf.x, endf.y, endf.z], &squared_euclidean) + .ok()? + .1 as usize; + kdtree.add(&[endf.x, endf.y, endf.z], node_index).ok()?; + nodes.push(endf); + parents.insert(node_index, nearest_index); + path.push(endf); + let mut current_node_index = node_index; + while current_node_index > 0 { + current_node_index = *parents.get(¤t_node_index).unwrap(); + path.push(nodes[current_node_index]); + } + + let mut terminals = Vec::new(); + let last = nodes.len() - 1; + for (node_id, node_pos) in nodes.iter().enumerate() { + if !parents.values().any(|e| e == &node_id) && node_id != 0 && node_id != last { + terminals.push(node_pos.map(|e| e.floor() as i32)); + } + } + + let branches = parents + .iter() + .map(|(a, b)| { + ( + nodes[*a].map(|e| e.floor() as i32), + nodes[*b].map(|e| e.floor() as i32), + ) + }) + .collect::, Vec3)>>(); + let path = path + .iter() + .map(|a| a.map(|e| e.floor() as i32)) + .collect::>>(); + + Some(Self { + start, + end, + branches, + path, + terminals, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_creating_entities() { + let pos = Vec3::zero(); + let mut rng = thread_rng(); + + gnarling_mugger(pos, &mut rng); + gnarling_stalker(pos, &mut rng); + gnarling_logger(pos, &mut rng); + gnarling_chieftain(pos, &mut rng); + deadwood(pos, &mut rng); + mandragora(pos, &mut rng); + wood_golem(pos, &mut rng); + harvester_boss(pos, &mut rng); + } +} diff --git a/world/src/site2/tile.rs b/world/src/site2/tile.rs index 4de6d01db8..eb98d8df0e 100644 --- a/world/src/site2/tile.rs +++ b/world/src/site2/tile.rs @@ -192,6 +192,7 @@ pub enum TileKind { Gate, GnarlingFortification, Bridge, + AdletStronghold, } #[derive(Clone, PartialEq)]