diff --git a/CHANGELOG.md b/CHANGELOG.md index b259b40706..e2f4374bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the ability to make pets sit, they wont follow nor defend you in this state - Portals that spawn in place of the last staircase at old style dungeons to prevent stair cheesing - Mutliple singleplayer worlds and map generation UI. +- New arena building in desert cities, suitable for PVP, also NPCs like to watch the fights too ### Changed diff --git a/assets/voxygen/i18n/en/npc.ftl b/assets/voxygen/i18n/en/npc.ftl index d80f37e8a8..a9336f9ef6 100644 --- a/assets/voxygen/i18n/en/npc.ftl +++ b/assets/voxygen/i18n/en/npc.ftl @@ -287,3 +287,4 @@ npc-speech-dist_far = far away npc-speech-dist_ahead = some way away npc-speech-dist_near = nearby npc-speech-dist_near_to = very close +npc-speech-arena = Let's sit over there! \ No newline at end of file diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index ea111de60b..6b48d8e2f5 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -6,6 +6,7 @@ use crate::{ character::CharacterId, comp::{dialogue::Subject, Content}, + util::Dir, }; use rand::{seq::IteratorRandom, Rng}; use serde::{Deserialize, Serialize}; @@ -230,6 +231,8 @@ pub struct RtSimController { pub actions: VecDeque, pub personality: Personality, pub heading_to: Option, + // TODO: Maybe this should allow for looking at a specific entity target? + pub look_dir: Option, } impl RtSimController { @@ -248,7 +251,9 @@ pub enum NpcActivity { Gather(&'static [ChunkResource]), // TODO: Generalise to other entities? What kinds of animals? HuntAnimals, - Dance, + Dance(Option), + Cheer(Option), + Sit(Option), } /// Represents event-like actions that rtsim NPCs can perform to interact with diff --git a/common/src/terrain/site.rs b/common/src/terrain/site.rs index ac576be988..e857268f77 100644 --- a/common/src/terrain/site.rs +++ b/common/src/terrain/site.rs @@ -23,5 +23,4 @@ pub enum SettlementKindMeta { DesertCity, SavannahPit, CoastalTown, - PirateHideout, } diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index 121a12e3b5..862d6a9ea3 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -14,6 +14,7 @@ use common::{ }, store::Id, terrain::CoordinateConversions, + util::Dir, }; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; @@ -57,6 +58,7 @@ pub struct Controller { pub actions: Vec, pub activity: Option, pub new_home: Option, + pub look_dir: Option, } impl Controller { @@ -72,7 +74,11 @@ impl Controller { pub fn do_hunt_animals(&mut self) { self.activity = Some(NpcActivity::HuntAnimals); } - pub fn do_dance(&mut self) { self.activity = Some(NpcActivity::Dance); } + pub fn do_dance(&mut self, dir: Option) { self.activity = Some(NpcActivity::Dance(dir)); } + + pub fn do_cheer(&mut self, dir: Option) { self.activity = Some(NpcActivity::Cheer(dir)); } + + pub fn do_sit(&mut self, dir: Option) { self.activity = Some(NpcActivity::Sit(dir)); } pub fn say(&mut self, target: impl Into>, content: comp::Content) { self.actions.push(NpcAction::Say(target.into(), content)); diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index 10c087685d..7bdbf8ab96 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -26,6 +26,7 @@ use common::{ store::Id, terrain::{CoordinateConversions, SiteKindMeta, TerrainChunkSize}, time::DayPeriod, + util::Dir, }; use fxhash::FxHasher64; use itertools::{Either, Itertools}; @@ -270,7 +271,7 @@ impl Rule for NpcAi { .collect::>() }; - // The sum of the last `SIMULATED_TICK_SKIP` tick deltatimes is the deltatime since + // The sum of the last `SIMULATED_TICK_SKIP` tick deltatimes is the deltatime since // simulated npcs ran this tick had their ai ran. let simulated_dt = last_ticks.iter().sum::(); @@ -283,6 +284,9 @@ impl Rule for NpcAi { .for_each(|(npc_id, controller, inbox, sentiments, known_reports, brain)| { let npc = &data.npcs[*npc_id]; + // Reset look_dir + controller.look_dir = None; + brain.action.tick(&mut NpcCtx { state: ctx.state, world: ctx.world, @@ -631,7 +635,7 @@ fn socialize() -> impl Action { if matches!(ctx.npc.mode, SimulationMode::Loaded) && socialize.should(ctx) { // Sometimes dance if ctx.rng.gen_bool(0.15) { - return just(|ctx, _| ctx.controller.do_dance()) + return just(|ctx, _| ctx.controller.do_dance(None)) .repeat() .stop_if(timeout(6.0)) .debug(|| "dancing") @@ -692,11 +696,11 @@ fn adventure() -> impl Action { .unwrap_or_default(); // Travel to the site important(just(move |ctx, _| ctx.controller.say(None, Content::localized_with_args("npc-speech-moving_on", [("site", site_name.clone())]))) - .then(travel_to_site(tgt_site, 0.6)) - // Stop for a few minutes - .then(villager(tgt_site).repeat().stop_if(timeout(wait_time))) - .map(|_, _| ()) - .boxed(), + .then(travel_to_site(tgt_site, 0.6)) + // Stop for a few minutes + .then(villager(tgt_site).repeat().stop_if(timeout(wait_time))) + .map(|_, _| ()) + .boxed(), ) } else { casual(finish().boxed()) @@ -841,6 +845,49 @@ fn villager(visiting_site: SiteId) -> impl Action { .debug(|| "find somewhere to sleep"), ); // Villagers with roles should perform those roles + } + // Visiting villagers in DesertCity who are not Merchants should sit down in the Arena during the day + else if matches!(ctx.state.data().sites[visiting_site].world_site.map(|ws| &ctx.index.sites.get(ws).kind), Some(SiteKind::DesertCity(_))) + && !matches!(ctx.npc.profession(), Some(Profession::Merchant | Profession::Guard)) + && ctx.rng.gen_bool(1.0 / 3.0) + { + let wait_time = ctx.rng.gen_range(100.0..300.0); + if let Some(ws_id) = ctx.state.data().sites[visiting_site].world_site + && let Some(ws) = ctx.index.sites.get(ws_id).site2() + && let Some(arena) = ws.plots().find_map(|p| match p.kind() { PlotKind::DesertCityArena(a) => Some(a), _ => None}) + { + // We don't use Z coordinates for seats because they are complicated to calculate from the Ramp procedural generation + // and using goto_2d seems to work just fine. However it also means that NPC will never go seat on the stands + // on the first floor of the arena. This is a compromise that was made because in the current arena procedural generation + // there is also no pathways to the stands on the first floor for NPCs. + let arena_center = Vec3::new(arena.center.x, arena.center.y, arena.base).as_::(); + let stand_dist = arena.stand_dist as f32; + let seat_var_width = ctx.rng.gen_range(0..arena.stand_width) as f32; + let seat_var_length = ctx.rng.gen_range(-arena.stand_length..arena.stand_length) as f32; + // Select a seat on one of the 4 arena stands + let seat = match ctx.rng.gen_range(0..4) { + 0 => Vec3::new(arena_center.x - stand_dist + seat_var_width, arena_center.y + seat_var_length, arena_center.z), + 1 => Vec3::new(arena_center.x + stand_dist - seat_var_width, arena_center.y + seat_var_length, arena_center.z), + 2 => Vec3::new(arena_center.x + seat_var_length, arena_center.y - stand_dist + seat_var_width, arena_center.z), + _ => Vec3::new(arena_center.x + seat_var_length, arena_center.y + stand_dist - seat_var_width, arena_center.z), + }; + let look_dir = Dir::from_unnormalized(arena_center - seat); + // Walk to an arena seat, cheer, sit and dance + return casual(just(move |ctx, _| ctx.controller.say(None, Content::localized("npc-speech-arena"))) + .then(goto_2d(seat.xy(), 0.6, 1.0).debug(|| "go to arena")) + // Turn toward the centre of the arena and watch the action! + .then(choose(move |ctx, _| if ctx.rng.gen_bool(0.3) { + casual(just(move |ctx,_| ctx.controller.do_cheer(look_dir)).repeat().stop_if(timeout(5.0))) + } else if ctx.rng.gen_bool(0.15) { + casual(just(move |ctx,_| ctx.controller.do_dance(look_dir)).repeat().stop_if(timeout(5.0))) + } else { + casual(just(move |ctx,_| ctx.controller.do_sit(look_dir)).repeat().stop_if(timeout(15.0))) + }) + .repeat() + .stop_if(timeout(wait_time))) + .map(|_, _| ()) + .boxed()); + } } else if matches!(ctx.npc.profession(), Some(Profession::Herbalist)) && ctx.rng.gen_bool(0.8) { if let Some(forest_wpos) = find_forest(ctx) { diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index 4a29480779..c16641e955 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -220,7 +220,9 @@ fn on_tick(ctx: EventCtx) { NpcActivity::Goto(_, _) | NpcActivity::Gather(_) | NpcActivity::HuntAnimals - | NpcActivity::Dance, + | NpcActivity::Dance(_) + | NpcActivity::Cheer(_) + | NpcActivity::Sit(_), ) => {}, None => {}, } @@ -246,7 +248,11 @@ fn on_tick(ctx: EventCtx) { } }, Some( - NpcActivity::Gather(_) | NpcActivity::HuntAnimals | NpcActivity::Dance, + NpcActivity::Gather(_) + | NpcActivity::HuntAnimals + | NpcActivity::Dance(_) + | NpcActivity::Cheer(_) + | NpcActivity::Sit(_), ) => { // TODO: Maybe they should walk around randomly // when gathering resources? diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index cc3a340c59..4e360044c1 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -238,6 +238,7 @@ impl<'a> AgentData<'a> { } agent.action_state.timers[ActionTimers::TimerIdle as usize] = 0.0; + 'activity: { match agent.rtsim_controller.activity { Some(NpcActivity::Goto(travel_to, speed_factor)) => { @@ -371,10 +372,46 @@ impl<'a> AgentData<'a> { controller.push_action(ControlAction::Dance); break 'activity; // Don't fall through to idle wandering }, - Some(NpcActivity::Dance) => { + Some(NpcActivity::Dance(dir)) => { + // Look at targets specified by rtsim + if let Some(look_dir) = dir { + controller.inputs.look_dir = look_dir; + if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 { + controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01; + break 'activity; + } else { + controller.inputs.move_dir = Vec2::zero(); + } + } controller.push_action(ControlAction::Dance); break 'activity; // Don't fall through to idle wandering }, + Some(NpcActivity::Cheer(dir)) => { + if let Some(look_dir) = dir { + controller.inputs.look_dir = look_dir; + if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 { + controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01; + break 'activity; + } else { + controller.inputs.move_dir = Vec2::zero(); + } + } + controller.push_action(ControlAction::Talk); + break 'activity; // Don't fall through to idle wandering + }, + Some(NpcActivity::Sit(dir)) => { + if let Some(look_dir) = dir { + controller.inputs.look_dir = look_dir; + if self.ori.look_dir().dot(look_dir.to_vec()) < 0.95 { + controller.inputs.move_dir = look_dir.to_vec().xy() * 0.01; + break 'activity; + } else { + controller.inputs.move_dir = Vec2::zero(); + } + } + controller.push_action(ControlAction::Sit); + break 'activity; // Don't fall through to idle wandering + }, Some(NpcActivity::HuntAnimals) => { if rng.gen::() < 0.1 { self.choose_target( diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 313f6001b7..aae1ab9cb2 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -440,6 +440,7 @@ impl<'a> System<'a> for Sys { // Update entity state if let Some(agent) = agent { agent.rtsim_controller.personality = npc.personality; + agent.rtsim_controller.look_dir = npc.controller.look_dir; agent.rtsim_controller.activity = npc.controller.activity; agent .rtsim_controller diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 721a5a3578..27a4bd8f20 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -261,19 +261,8 @@ impl Civs { let world_dims = ctx.sim.get_aabr(); for _ in 0..initial_civ_count * 3 { attempt(5, || { - let (loc, kind) = match ctx.rng.gen_range(0..84) { - 0..=5 => ( - find_site_loc( - &mut ctx, - &ProximityRequirementsBuilder::new() - .avoid_all_of(this.castle_enemies(), 40) - .close_to_one_of(this.towns(), 20) - .finalize(&world_dims), - &SiteKind::Castle, - )?, - SiteKind::Castle, - ), - 28..=31 => { + let (loc, kind) = match ctx.rng.gen_range(0..79) { + 0..=4 => { if index.features().site2_giant_trees { ( find_site_loc( @@ -298,7 +287,7 @@ impl Civs { ) } }, - 32..=37 => ( + 5..=10 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -308,8 +297,7 @@ impl Civs { )?, SiteKind::Gnarling, ), - // 32..=37 => (SiteKind::Citadel, (&castle_enemies, 20)), - 38..=43 => ( + 11..=16 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -319,7 +307,7 @@ impl Civs { )?, SiteKind::ChapelSite, ), - 44..=49 => ( + 17..=22 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -329,17 +317,7 @@ impl Civs { )?, SiteKind::Adlet, ), - /*50..=55 => ( - find_site_loc( - &mut ctx, - &ProximityRequirementsBuilder::new() - .avoid_all_of(this.mine_site_enemies(), 40) - .finalize(&world_dims), - &SiteKind::DwarvenMine, - )?, - SiteKind::DwarvenMine, - ),*/ - 56..=68 => ( + 23..=35 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -349,7 +327,7 @@ impl Civs { )?, SiteKind::PirateHideout, ), - 69..=75 => ( + 36..=42 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -359,6 +337,29 @@ impl Civs { )?, SiteKind::JungleRuin, ), + /*43..=48 => ( + find_site_loc( + &mut ctx, + &ProximityRequirementsBuilder::new() + .avoid_all_of(this.mine_site_enemies(), 40) + .finalize(&world_dims), + &SiteKind::DwarvenMine, + )?, + SiteKind::DwarvenMine, + ), + 49..=54 => ( + find_site_loc( + &mut ctx, + &ProximityRequirementsBuilder::new() + .avoid_all_of(this.castle_enemies(), 40) + .close_to_one_of(this.towns(), 20) + .finalize(&world_dims), + &SiteKind::Castle, + )?, + SiteKind::Castle, + ), + 55..=60 => (SiteKind::Citadel, (&castle_enemies, 20)), + */ _ => ( find_site_loc( &mut ctx, diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 2fd6525f86..7418d985a4 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -373,7 +373,6 @@ impl Site { | SiteKind::CliffTown(_) | SiteKind::SavannahPit(_) | SiteKind::CoastalTown(_) - | SiteKind::PirateHideout(_) | SiteKind::DesertCity(_) | SiteKind::Settlement(_) ) @@ -417,9 +416,6 @@ impl SiteKind { SiteKind::CoastalTown(_) => { Some(SiteKindMeta::Settlement(SettlementKindMeta::CoastalTown)) }, - SiteKind::PirateHideout(_) => { - Some(SiteKindMeta::Settlement(SettlementKindMeta::PirateHideout)) - }, SiteKind::DesertCity(_) => { Some(SiteKindMeta::Settlement(SettlementKindMeta::DesertCity)) }, diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 4e84a5ce04..6960d05082 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -1144,6 +1144,29 @@ impl Site { site.make_plaza(land, &mut rng); + let size = 17.0 as i32; + let aabr = Aabr { + min: Vec2::broadcast(-size), + max: Vec2::broadcast(size), + }; + + let desert_city_arena = + plot::DesertCityArena::generate(land, &mut reseed(&mut rng), &site, aabr); + + let desert_city_arena_alt = desert_city_arena.alt; + let plot = site.create_plot(Plot { + kind: PlotKind::DesertCityArena(desert_city_arena), + 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(desert_city_arena_alt), + }); + let build_chance = Lottery::from(vec![(20.0, 1), (10.0, 2)]); let mut temples = 0; @@ -1675,6 +1698,9 @@ impl Site { PlotKind::DesertCityTemple(desert_city_temple) => { desert_city_temple.render_collect(self, canvas) }, + PlotKind::DesertCityArena(desert_city_arena) => { + desert_city_arena.render_collect(self, canvas) + }, PlotKind::Citadel(citadel) => citadel.render_collect(self, canvas), PlotKind::Bridge(bridge) => bridge.render_collect(self, canvas), PlotKind::PirateHideout(pirate_hideout) => { diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 26394dd906..256d433780 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -5,6 +5,7 @@ mod citadel; mod cliff_tower; mod coastal_house; mod coastal_workshop; +mod desert_city_arena; mod desert_city_multiplot; mod desert_city_temple; pub mod dungeon; @@ -23,9 +24,9 @@ mod workshop; pub use self::{ adlet::AdletStronghold, bridge::Bridge, castle::Castle, citadel::Citadel, cliff_tower::CliffTower, coastal_house::CoastalHouse, coastal_workshop::CoastalWorkshop, - desert_city_multiplot::DesertCityMultiPlot, desert_city_temple::DesertCityTemple, - dungeon::Dungeon, dwarven_mine::DwarvenMine, giant_tree::GiantTree, - gnarling::GnarlingFortification, house::House, jungle_ruin::JungleRuin, + desert_city_arena::DesertCityArena, desert_city_multiplot::DesertCityMultiPlot, + desert_city_temple::DesertCityTemple, dungeon::Dungeon, dwarven_mine::DwarvenMine, + giant_tree::GiantTree, gnarling::GnarlingFortification, house::House, jungle_ruin::JungleRuin, pirate_hideout::PirateHideout, savannah_hut::SavannahHut, savannah_pit::SavannahPit, savannah_workshop::SavannahWorkshop, sea_chapel::SeaChapel, workshop::Workshop, }; @@ -74,6 +75,7 @@ pub enum PlotKind { Workshop(Workshop), DesertCityMultiPlot(DesertCityMultiPlot), DesertCityTemple(DesertCityTemple), + DesertCityArena(DesertCityArena), SeaChapel(SeaChapel), JungleRuin(JungleRuin), Plaza, diff --git a/world/src/site2/plot/desert_city_arena.rs b/world/src/site2/plot/desert_city_arena.rs new file mode 100644 index 0000000000..2cb89b894e --- /dev/null +++ b/world/src/site2/plot/desert_city_arena.rs @@ -0,0 +1,1267 @@ +use std::{f32::consts::TAU, sync::Arc}; + +use crate::{ + site2::{Dir, Fill, Painter, Site, Structure}, + util::{RandomField, Sampler, CARDINALS, DIAGONALS}, + Land, +}; +use common::{ + generation::{EntityInfo, SpecialEntity}, + terrain::{Block, BlockKind, SpriteKind}, +}; +use rand::Rng; +use vek::*; + +use super::dungeon::spiral_staircase; + +pub struct DesertCityArena { + /// Approximate altitude of the door tile + pub(crate) alt: i32, + pub base: i32, + pub center: Vec2, + // arena + // config + length: i32, + width: i32, + height: i32, + corner: i32, + wall_th: i32, + pillar_size: i32, + top_height: i32, + pub stand_dist: i32, + pub stand_length: i32, + pub stand_width: i32, +} + +impl DesertCityArena { + pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr) -> Self { + let bounds = Aabr { + min: site.tile_wpos(tile_aabr.min), + max: site.tile_wpos(tile_aabr.max), + }; + let alt = land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2)) + as i32 + + 2; + let base = alt + 1; + let center = bounds.center(); + // arena + // config + let length = 160; + let width = length / 2; + let height = length / 6; + let corner = length / 5; + let wall_th = 3; + let pillar_size = (length / 15) - wall_th; + let top_height = 3; + let stand_dist = length / 3; + let stand_length = length / 6; + let stand_width = length / 16; + + Self { + alt, + base, + center, + length, + width, + height, + corner, + wall_th, + pillar_size, + top_height, + stand_dist, + stand_length, + stand_width, + } + } + + pub fn radius(&self) -> f32 { 100.0 } + + pub fn entity_at( + &self, + _pos: Vec3, + _above_block: &Block, + _dynamic_rng: &mut impl Rng, + ) -> Option { + None + } +} + +impl Structure for DesertCityArena { + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"render_arena\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "render_arena")] + fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) { + let base = self.base; + let center = self.center; + + // arena + // config + let length = self.length; + let width = self.width; + let height = self.height; + let corner = self.corner; + let wall_th = self.wall_th; + let pillar_size = self.pillar_size; + let top_height = self.top_height; + + let sandstone = Fill::Sampling(Arc::new(|center| { + Some(match (RandomField::new(0).get(center)) % 37 { + 0..=8 => Block::new(BlockKind::Rock, Rgb::new(245, 212, 129)), + 9..=17 => Block::new(BlockKind::Rock, Rgb::new(246, 214, 133)), + 18..=26 => Block::new(BlockKind::Rock, Rgb::new(247, 216, 136)), + 27..=35 => Block::new(BlockKind::Rock, Rgb::new(248, 219, 142)), + _ => Block::new(BlockKind::Rock, Rgb::new(235, 178, 99)), + }) + })); + let color = Fill::Block(Block::new(BlockKind::Rock, Rgb::new(19, 48, 76))); + let chain = Fill::Block(Block::air(SpriteKind::SeaDecorChain)); + let lantern = Fill::Block(Block::air(SpriteKind::Lantern)); + + // clear area for entries + for l in 0..8 { + painter + .aabb(Aabb { + min: (center - (length / 2) - l).with_z(base + l), + max: (center + (length / 2) + l).with_z(base + 1 + l), + }) + .clear(); + } + // top + let top_1 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - (2 * wall_th), + center.y - (width / 2) - (2 * wall_th), + ) + .with_z(base + height + wall_th), + max: Vec2::new( + center.x + (length / 2) + (2 * wall_th), + center.y + (width / 2) + (2 * wall_th), + ) + .with_z(base + height + wall_th + top_height), + }); + let top_2 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - (2 * wall_th) + corner, + center.y - (width / 2) - corner - (2 * wall_th), + ) + .with_z(base + height + wall_th), + max: Vec2::new( + center.x + (length / 2) + (2 * wall_th) - corner, + center.y + (width / 2) + corner + (2 * wall_th), + ) + .with_z(base + height + wall_th + top_height), + }); + top_1.union(top_2).fill(sandstone.clone()); + let top_carve_1 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - (2 * wall_th) + 1, + center.y - (width / 2) - (2 * wall_th) + 1, + ) + .with_z(base + height + wall_th + top_height - 2), + max: Vec2::new( + center.x + (length / 2) + (2 * wall_th) - 1, + center.y + (width / 2) + (2 * wall_th) - 1, + ) + .with_z(base + height + wall_th + top_height), + }); + let top_carve_2 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - (2 * wall_th) + corner + 1, + center.y - (width / 2) - corner - (2 * wall_th) + 1, + ) + .with_z(base + height + wall_th + top_height - 2), + max: Vec2::new( + center.x + (length / 2) + (2 * wall_th) - corner - 1, + center.y + (width / 2) + corner + (2 * wall_th) - 1, + ) + .with_z(base + height + wall_th + 3), + }); + top_carve_1.union(top_carve_2).clear(); + + let carve_dots = 80.0_f32; + let carve_dots_radius = length + (length / 2); + let phi_carve_dots = TAU / carve_dots; + for n in 1..=carve_dots as i32 { + let dot_pos = Vec2::new( + center.x + (carve_dots_radius as f32 * ((n as f32 * phi_carve_dots).cos())) as i32, + center.y + (carve_dots_radius as f32 * ((n as f32 * phi_carve_dots).sin())) as i32, + ); + // top decor carve + painter + .line( + center.with_z(base + height + wall_th + 2), + dot_pos.with_z(base + height + wall_th + 2), + 1.5, + ) + .clear(); + } + + // pillars and spires + let mut pillar_positions = vec![]; + let mut pillars = vec![]; + let mut spire_positions = vec![]; + + for dir in DIAGONALS { + let inner_square_pos = center + (dir * ((length / 2) - corner - wall_th)); + let outer_square_pos_1 = Vec2::new( + center.x + (dir.x * ((length / 2) + (corner / 8) - wall_th)), + center.y + (dir.y * ((width / 2) - wall_th)), + ); + let outer_square_pos_2 = Vec2::new( + center.x + (dir.x * ((length / 2) - corner - wall_th)), + center.y + (dir.y * ((width / 2) + (5 * (corner / 4)) - wall_th)), + ); + pillar_positions.push(inner_square_pos); + pillar_positions.push(outer_square_pos_1); + pillar_positions.push(outer_square_pos_2); + + let spire_pos_1 = Vec2::new( + center.x + (dir.x * (length / 10)), + center.y + (dir.y * (2 * (length / 5))), + ); + + let spire_pos_2 = Vec2::new( + center.x + (dir.y * (2 * (length / 5))), + center.y + (dir.x * (length / 11)), + ); + spire_positions.push(spire_pos_1); + spire_positions.push(spire_pos_2); + } + for pillar_pos in pillar_positions { + let height_var = (RandomField::new(0).get(pillar_pos.with_z(base)) % 20) as i32; + let pillar_height = height + 8 + height_var; + pillars.push((pillar_pos, pillar_height)); + } + for (pillar_pos, pillar_height) in &pillars { + // pillar + painter + .aabb(Aabb { + min: (pillar_pos - pillar_size - wall_th).with_z(base - 10), + max: (pillar_pos + pillar_size + wall_th) + .with_z(base + pillar_height + wall_th), + }) + .fill(sandstone.clone()); + // carve large + painter + .aabb(Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - wall_th, + pillar_pos.y - pillar_size, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size + wall_th, + pillar_pos.y + pillar_size, + ) + .with_z(base + pillar_height), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size, + pillar_pos.y - pillar_size - wall_th, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size, + pillar_pos.y + pillar_size + wall_th, + ) + .with_z(base + pillar_height), + }) + .clear(); + // carve small + for dir in DIAGONALS { + for c in 0..((pillar_height / 2) - 1) { + let carve_pos = pillar_pos + (dir * (pillar_size + wall_th)); + painter + .aabb(Aabb { + min: (carve_pos - 1).with_z(base + wall_th + (c * 2)), + max: (carve_pos + 1).with_z(base + 1 + wall_th + (c * 2)), + }) + .clear(); + } + } + // upper decor + for d in 0..3 { + // d1 + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - 1, + pillar_pos.y - 7 + (d * 5), + ) + .with_z(base + pillar_height - 6), + max: Vec2::new( + pillar_pos.x + pillar_size + 1, + pillar_pos.y - 3 + (d * 5), + ) + .with_z(base + pillar_height), + }, + Dir::X, + ) + .fill(sandstone.clone()); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - 1, + pillar_pos.y - 6 + (d * 5), + ) + .with_z(base + pillar_height - 5), + max: Vec2::new( + pillar_pos.x + pillar_size + 1, + pillar_pos.y - 4 + (d * 5), + ) + .with_z(base + pillar_height - 1), + }, + Dir::X, + ) + .clear(); + + // d2 + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + pillar_pos.x - 7 + (d * 5), + pillar_pos.y - pillar_size - 1, + ) + .with_z(base + pillar_height - 6), + max: Vec2::new( + pillar_pos.x - 3 + (d * 5), + pillar_pos.y + pillar_size + 1, + ) + .with_z(base + pillar_height), + }, + Dir::Y, + ) + .fill(sandstone.clone()); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + pillar_pos.x - 6 + (d * 5), + pillar_pos.y - pillar_size - 1, + ) + .with_z(base + pillar_height - 5), + max: Vec2::new( + pillar_pos.x - 4 + (d * 5), + pillar_pos.y + pillar_size + 1, + ) + .with_z(base + pillar_height - 1), + }, + Dir::Y, + ) + .clear(); + } + // arches + // a1 + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size, + pillar_pos.y - pillar_size - wall_th + 1, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size, + pillar_pos.y + pillar_size + wall_th - 1, + ) + .with_z(base + (4 * pillar_size) + 2), + }, + Dir::Y, + ) + .fill(sandstone.clone()); + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size + 2, + pillar_pos.y - pillar_size - wall_th + 1, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size - 2, + pillar_pos.y + pillar_size + wall_th - 1, + ) + .with_z(base + (4 * pillar_size)), + }, + Dir::Y, + ) + .clear(); + // a2 + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size + 2, + pillar_pos.y - pillar_size - wall_th + 2, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size - 2, + pillar_pos.y + pillar_size + wall_th - 2, + ) + .with_z(base + (4 * pillar_size)), + }, + Dir::Y, + ) + .fill(sandstone.clone()); + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size + 4, + pillar_pos.y - pillar_size - wall_th + 2, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size - 4, + pillar_pos.y + pillar_size + wall_th - 2, + ) + .with_z(base + (4 * pillar_size) - 2), + }, + Dir::Y, + ) + .clear(); + // b1 + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - wall_th + 1, + pillar_pos.y - pillar_size, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size + wall_th - 1, + pillar_pos.y + pillar_size, + ) + .with_z(base + (4 * pillar_size) + 2), + }, + Dir::X, + ) + .fill(sandstone.clone()); + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - wall_th + 1, + pillar_pos.y - pillar_size + 2, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size + wall_th - 1, + pillar_pos.y + pillar_size - 2, + ) + .with_z(base + (4 * pillar_size)), + }, + Dir::X, + ) + .clear(); + // b2 + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - wall_th + 2, + pillar_pos.y - pillar_size + 2, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size + wall_th - 2, + pillar_pos.y + pillar_size - 2, + ) + .with_z(base + (4 * pillar_size)), + }, + Dir::X, + ) + .fill(sandstone.clone()); + painter + .vault( + Aabb { + min: Vec2::new( + pillar_pos.x - pillar_size - wall_th + 2, + pillar_pos.y - pillar_size + 4, + ) + .with_z(base), + max: Vec2::new( + pillar_pos.x + pillar_size + wall_th - 2, + pillar_pos.y + pillar_size - 4, + ) + .with_z(base + (4 * pillar_size) - 2), + }, + Dir::X, + ) + .clear(); + // top + painter + .aabb(Aabb { + min: (pillar_pos - pillar_size - (2 * wall_th)) + .with_z(base + pillar_height + wall_th), + max: (pillar_pos + pillar_size + (2 * wall_th)) + .with_z(base + pillar_height + wall_th + top_height), + }) + .fill(sandstone.clone()); + painter + .aabb(Aabb { + min: (pillar_pos - pillar_size - (2 * wall_th) + 1) + .with_z(base + pillar_height + wall_th + top_height - 2), + max: (pillar_pos + pillar_size + (2 * wall_th) - 1) + .with_z(base + pillar_height + wall_th + top_height), + }) + .clear(); + + let pillar_inlay = painter.aabb(Aabb { + min: (pillar_pos - pillar_size).with_z(base), + max: (pillar_pos + pillar_size).with_z(base + pillar_height), + }); + pillar_inlay.fill(color.clone()); + // decor + for r in 0..(pillar_height - 3) { + let dots = 8.0_f32 + (r / 3) as f32; + let dots_radius = 2 * pillar_size; + let phi_dots = TAU / dots; + for n in 1..=dots as i32 { + let dot_pos = Vec2::new( + pillar_pos.x + (dots_radius as f32 * ((n as f32 * phi_dots).cos())) as i32, + pillar_pos.y + (dots_radius as f32 * ((n as f32 * phi_dots).sin())) as i32, + ); + if dots == 16.0_f32 { + // top decor carve + painter + .line( + pillar_pos.with_z(base + pillar_height + wall_th + 2), + dot_pos.with_z(base + pillar_height + wall_th + 2), + 1.5, + ) + .clear(); + } + // dots + painter + .line( + pillar_pos.with_z(base + (r * 2)), + dot_pos.with_z(base + (r * 2)), + 0.5, + ) + .intersect(pillar_inlay) + .fill(sandstone.clone()); + } + } + } + + // arena + let outer_aabb_1 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - wall_th, + center.y - (width / 2) - wall_th, + ) + .with_z(base - 10), + max: Vec2::new( + center.x + (length / 2) + wall_th, + center.y + (width / 2) + wall_th, + ) + .with_z(base + height + wall_th), + }); + let outer_aabb_2 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - wall_th + corner, + center.y - (width / 2) - corner - wall_th, + ) + .with_z(base - 10), + max: Vec2::new( + center.x + (length / 2) + wall_th - corner, + center.y + (width / 2) + corner + wall_th, + ) + .with_z(base + height + wall_th), + }); + outer_aabb_1.union(outer_aabb_2).fill(sandstone.clone()); + // decor carve + let clear_aabb_1 = painter.aabb(Aabb { + min: Vec2::new(center.x - (length / 2) - wall_th, center.y - (width / 2)).with_z(base), + max: Vec2::new(center.x + (length / 2) + wall_th, center.y + (width / 2)) + .with_z(base + height), + }); + let clear_aabb_2 = painter.aabb(Aabb { + min: Vec2::new(center.x - (length / 2), center.y - (width / 2) - wall_th).with_z(base), + max: Vec2::new(center.x + (length / 2), center.y + (width / 2) + wall_th) + .with_z(base + height), + }); + clear_aabb_1.union(clear_aabb_2).clear(); + let clear_aabb_3 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) - wall_th + corner, + center.y - (width / 2) - corner, + ) + .with_z(base), + max: Vec2::new( + center.x + (length / 2) + wall_th - corner, + center.y + (width / 2) + corner, + ) + .with_z(base + height), + }); + let clear_aabb_4 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) + corner, + center.y - (width / 2) - wall_th - corner, + ) + .with_z(base), + max: Vec2::new( + center.x + (length / 2) - corner, + center.y + (width / 2) + wall_th + corner, + ) + .with_z(base + height), + }); + clear_aabb_3.union(clear_aabb_4).clear(); + // color inlay + let inlay_aabb_1 = painter.aabb(Aabb { + min: Vec2::new(center.x - (length / 2), center.y - (width / 2)).with_z(base), + max: Vec2::new(center.x + (length / 2), center.y + (width / 2)).with_z(base + height), + }); + let inlay_aabb_2 = painter.aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) + corner, + center.y - (width / 2) - corner, + ) + .with_z(base), + max: Vec2::new( + center.x + (length / 2) - corner, + center.y + (width / 2) + corner, + ) + .with_z(base + height), + }); + inlay_aabb_1.union(inlay_aabb_2).fill(color.clone()); + let inlay = inlay_aabb_1.union(inlay_aabb_2); + for r in 0..(height - 3) { + let dots = 50.0_f32 + (3 * r) as f32; + let dots_radius = length + (length / 2); + let phi_dots = TAU / dots; + for n in 1..=dots as i32 { + let dot_pos = Vec2::new( + center.x + (dots_radius as f32 * ((n as f32 * phi_dots).cos())) as i32, + center.y + (dots_radius as f32 * ((n as f32 * phi_dots).sin())) as i32, + ); + // color decor + painter + .line( + center.with_z(base + (r * 2)), + dot_pos.with_z(base + (r * 2)), + 1.0, + ) + .intersect(inlay) + .fill(sandstone.clone()); + } + } + // entries + painter + .vault( + Aabb { + min: Vec2::new(center.x - (length / 2) - wall_th, center.y - 10).with_z(base), + max: Vec2::new(center.x + (length / 2) + wall_th, center.y + 10) + .with_z(base + (height / 2) + 8), + }, + Dir::X, + ) + .fill(sandstone.clone()); + painter + .vault( + Aabb { + min: Vec2::new(center.x - 10, center.y - (width / 2) - corner - wall_th) + .with_z(base), + max: Vec2::new(center.x + 10, center.y + (width / 2) + corner + wall_th) + .with_z(base + (height / 2) + 8), + }, + Dir::Y, + ) + .fill(sandstone.clone()); + painter + .vault( + Aabb { + min: Vec2::new(center.x - (length / 2) - wall_th, center.y - 10 + wall_th) + .with_z(base), + max: Vec2::new(center.x + (length / 2) + wall_th, center.y + 10 - wall_th) + .with_z(base + (height / 2) + 8 - wall_th), + }, + Dir::X, + ) + .clear(); + painter + .vault( + Aabb { + min: Vec2::new( + center.x - 10 + wall_th, + center.y - (width / 2) - corner - wall_th, + ) + .with_z(base), + max: Vec2::new( + center.x + 10 - wall_th, + center.y + (width / 2) + corner + wall_th, + ) + .with_z(base + (height / 2) + 8 - wall_th), + }, + Dir::Y, + ) + .clear(); + // center clear + painter + .aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) + corner + (2 * wall_th) + pillar_size, + center.y - (length / 2) + corner + (2 * wall_th) + pillar_size, + ) + .with_z(base + height), + max: Vec2::new( + center.x + (length / 2) - corner - (2 * wall_th) - pillar_size, + center.y + (length / 2) - corner - (2 * wall_th) - pillar_size, + ) + .with_z(base + height + wall_th + top_height - 2), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new( + center.x - (length / 2) + corner + wall_th, + center.y - (length / 2) + corner + wall_th, + ) + .with_z(base - 1), + max: Vec2::new( + center.x + (length / 2) - corner - wall_th, + center.y + (length / 2) - corner - wall_th, + ) + .with_z(base + height), + }) + .clear(); + // center decor + for d in 0..((length / 12) - 2) { + // d1 + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - 3 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y - (length / 2) + corner + (2 * wall_th) + pillar_size - 1, + ) + .with_z(base + height + wall_th + top_height - 7), + max: Vec2::new( + center.x + 3 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y + (length / 2) - corner - (2 * wall_th) - pillar_size + 1, + ) + .with_z(base + height + wall_th + top_height - 1), + }, + Dir::Y, + ) + .fill(sandstone.clone()); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - 3 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y - (length / 2) + corner + (2 * wall_th) + pillar_size, + ) + .with_z(base + height + wall_th + top_height - 7), + max: Vec2::new( + center.x + 3 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y + (length / 2) - corner - (2 * wall_th) - pillar_size, + ) + .with_z(base + height + wall_th + top_height - 1), + }, + Dir::Y, + ) + .clear(); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - 2 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y - (length / 2) + corner + (2 * wall_th) + pillar_size - 2, + ) + .with_z(base + height + wall_th + top_height - 6), + max: Vec2::new( + center.x + 2 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y + (length / 2) - corner - (2 * wall_th) - pillar_size + 2, + ) + .with_z(base + height + wall_th + top_height - 2), + }, + Dir::Y, + ) + .fill(color.clone()); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - 2 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y - (length / 2) + corner + (2 * wall_th) + pillar_size - 1, + ) + .with_z(base + height + wall_th + top_height - 6), + max: Vec2::new( + center.x + 2 - (length / 2) + + corner + + (3 * wall_th) + + pillar_size + + (6 * d) + + 2, + center.y + (length / 2) - corner - (2 * wall_th) - pillar_size + 1, + ) + .with_z(base + height + wall_th + top_height - 2), + }, + Dir::Y, + ) + .clear(); + } + for e in 0..(length / 14) { + // d2 + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - (length / 2) + corner + (2 * wall_th) + pillar_size - 1, + center.y - 3 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 7), + max: Vec2::new( + center.x + (length / 2) - corner - (2 * wall_th) - pillar_size + 1, + center.y + 3 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 1), + }, + Dir::X, + ) + .fill(sandstone.clone()); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - (length / 2) + corner + (2 * wall_th) + pillar_size, + center.y - 3 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 7), + max: Vec2::new( + center.x + (length / 2) - corner - (2 * wall_th) - pillar_size, + center.y + 3 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 1), + }, + Dir::X, + ) + .clear(); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - (length / 2) + corner + (2 * wall_th) + pillar_size - 2, + center.y - 2 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 6), + max: Vec2::new( + center.x + (length / 2) - corner - (2 * wall_th) - pillar_size + 2, + center.y + 2 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 2), + }, + Dir::X, + ) + .fill(color.clone()); + painter + .horizontal_cylinder( + Aabb { + min: Vec2::new( + center.x - (length / 2) + corner + (2 * wall_th) + pillar_size - 1, + center.y - 2 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 6), + max: Vec2::new( + center.x + (length / 2) - corner - (2 * wall_th) - pillar_size + 1, + center.y + 2 - (length / 2) + + corner + + (2 * wall_th) + + pillar_size + + (6 * e) + + 5, + ) + .with_z(base + height + wall_th + top_height - 2), + }, + Dir::X, + ) + .clear(); + } + + // entry steps + for dir in CARDINALS { + let step_pos = Vec2::new( + center.x + (dir.x * (length / 2)), + center.y + (dir.y * ((width / 2) + corner)), + ); + for s in 0..9 { + painter + .aabb(Aabb { + min: (step_pos - 7 - s).with_z(base - 2 - s), + max: (step_pos + 7 + s).with_z(base - 1 - s), + }) + .fill(sandstone.clone()); + } + } + // clear rooms + for r in 0..2 { + let room_pos_1 = Vec2::new( + center.x - (length / 2) + (length / 8) + (r * (length - (length / 4))), + center.y, + ); + let room_pos_2 = Vec2::new( + center.x, + center.y - (width / 2) - (corner / 2) + (r * (width + corner)), + ); + + painter + .aabb(Aabb { + min: Vec2::new( + room_pos_1.x - (length / 8) + wall_th, + room_pos_1.y - (width / 2) + wall_th, + ) + .with_z(base - 1), + max: Vec2::new( + room_pos_1.x + (length / 8) - wall_th, + room_pos_1.y + (width / 2) - wall_th, + ) + .with_z(base + height), + }) + .clear(); + painter + .aabb(Aabb { + min: Vec2::new( + room_pos_2.x - (length / 2) + corner + wall_th, + room_pos_2.y - (corner / 2) + wall_th, + ) + .with_z(base - 1), + max: Vec2::new( + room_pos_2.x + (length / 2) - corner - wall_th, + room_pos_2.y + (corner / 2) - wall_th, + ) + .with_z(base + height), + }) + .clear(); + // stands + for s in 0..1 { + // distance from center + let stand_dist = self.stand_dist; + let stand_length = self.stand_length; + let stand_width = self.stand_width; + let floor = s * (height + wall_th + top_height - 1); + + painter + .ramp_inset( + Aabb { + min: Vec2::new(center.x - stand_length - 1, center.y - stand_dist) + .with_z(base - 1 + floor), + max: Vec2::new( + center.x + stand_length + 1, + center.y - stand_dist + stand_width, + ) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::NegY, + ) + .fill(color.clone()); + painter + .ramp_inset( + Aabb { + min: Vec2::new(center.x - stand_length, center.y - stand_dist) + .with_z(base - 1 + floor), + max: Vec2::new( + center.x + stand_length, + center.y - stand_dist + stand_width, + ) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::NegY, + ) + .fill(sandstone.clone()); + painter + .ramp_inset( + Aabb { + min: Vec2::new( + center.x - stand_length - 1, + center.y + stand_dist - stand_width, + ) + .with_z(base - 1 + floor), + max: Vec2::new(center.x + stand_length + 1, center.y + stand_dist) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::Y, + ) + .fill(color.clone()); + + painter + .ramp_inset( + Aabb { + min: Vec2::new( + center.x - stand_length, + center.y + stand_dist - stand_width, + ) + .with_z(base - 1 + floor), + max: Vec2::new(center.x + stand_length, center.y + stand_dist) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::Y, + ) + .fill(sandstone.clone()); + painter + .ramp_inset( + Aabb { + min: Vec2::new(center.x - stand_dist, center.y - stand_length - 1) + .with_z(base - 1 + floor), + max: Vec2::new( + center.x - stand_dist + stand_width, + center.y + stand_length + 1, + ) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::NegX, + ) + .fill(color.clone()); + painter + .ramp_inset( + Aabb { + min: Vec2::new(center.x - stand_dist, center.y - stand_length) + .with_z(base - 1 + floor), + max: Vec2::new( + center.x - stand_dist + stand_width, + center.y + stand_length, + ) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::NegX, + ) + .fill(sandstone.clone()); + painter + .ramp_inset( + Aabb { + min: Vec2::new( + center.x + stand_dist - stand_width, + center.y - stand_length - 1, + ) + .with_z(base - 1 + floor), + max: Vec2::new(center.x + stand_dist, center.y + stand_length + 1) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::X, + ) + .fill(color.clone()); + painter + .ramp_inset( + Aabb { + min: Vec2::new( + center.x + stand_dist - stand_width, + center.y - stand_length, + ) + .with_z(base - 1 + floor), + max: Vec2::new(center.x + stand_dist, center.y + stand_length) + .with_z(base + (length / 16) - 1 + floor), + }, + length / 16, + Dir::X, + ) + .fill(sandstone.clone()); + } + } + for (pillar_pos, pillar_height) in pillars { + let stairs_radius = pillar_size - 1; + let stairs = painter.aabb(Aabb { + min: (pillar_pos - stairs_radius).with_z(base - 1), + max: (pillar_pos + stairs_radius).with_z(base + pillar_height + wall_th + 1), + }); + stairs.clear(); + stairs + .sample(spiral_staircase( + pillar_pos.with_z(base + pillar_height + wall_th + 1), + (stairs_radius + 2) as f32, + 0.5, + ((pillar_height + wall_th + top_height) / 3) as f32, + )) + .fill(sandstone.clone()); + } + for spire_pos in &spire_positions { + let spire_height = + (height / 3) + (RandomField::new(0).get(spire_pos.with_z(base)) % 6) as i32; + painter + .cylinder(Aabb { + min: (spire_pos - pillar_size - 1) + .with_z(base + height + wall_th + top_height - 2), + max: (spire_pos + pillar_size + 2) + .with_z(base + height + wall_th + top_height + 6), + }) + .fill(sandstone.clone()); + painter + .cylinder(Aabb { + min: (spire_pos - pillar_size).with_z(base + height + wall_th + top_height + 6), + max: (spire_pos + pillar_size + 1) + .with_z(base + height + wall_th + top_height + spire_height), + }) + .fill(color.clone()); + for r in 0..((spire_height / 2) - 3) { + let spire_dots = 16.0_f32 + (2 * r) as f32; + let spire_dots_radius = pillar_size as f32 + 0.5; + let phi_spire_dots = TAU / spire_dots; + + for n in 1..=spire_dots as i32 { + let spire_dot_pos = Vec2::new( + spire_pos.x + + (spire_dots_radius * ((n as f32 * phi_spire_dots).cos())) as i32, + spire_pos.y + + (spire_dots_radius * ((n as f32 * phi_spire_dots).sin())) as i32, + ); + // color decor + painter + .line( + spire_pos.with_z(base + height + wall_th + top_height + 6 + (r * 2)), + spire_dot_pos + .with_z(base + height + wall_th + top_height + 6 + (r * 2)), + 1.0, + ) + .fill(sandstone.clone()); + } + } + + painter + .cylinder(Aabb { + min: (spire_pos - pillar_size - 1) + .with_z(base + height + wall_th + top_height + spire_height), + max: (spire_pos + pillar_size + 2) + .with_z(base + height + wall_th + top_height + spire_height + 1), + }) + .fill(sandstone.clone()); + painter + .sphere(Aabb { + min: (spire_pos - pillar_size) + .with_z(base + height + wall_th + top_height + spire_height - pillar_size), + max: (spire_pos + pillar_size + 1) + .with_z(base + height + wall_th + top_height + spire_height + pillar_size), + }) + .fill(color.clone()); + painter + .cone(Aabb { + min: (spire_pos - 2) + .with_z(base + height + wall_th + top_height + spire_height + pillar_size), + max: (spire_pos + 3).with_z( + base + height + + wall_th + + top_height + + spire_height + + pillar_size + + (spire_height / 3), + ), + }) + .fill(sandstone.clone()); + // campfires & repair benches + painter.spawn( + EntityInfo::at((spire_pos - 2).with_z(base - 1).map(|e| e as f32)) + .into_special(SpecialEntity::Waypoint), + ); + painter.sprite((spire_pos + 2).with_z(base - 1), SpriteKind::RepairBench); + + // lamps + let lamps = 8.0_f32; + let lamps_radius = 3; + let phi_lamps = TAU / lamps; + for n in 1..=lamps as i32 { + let lamp_pos = Vec2::new( + spire_pos.x + (lamps_radius as f32 * ((n as f32 * phi_lamps).cos())) as i32, + spire_pos.y + (lamps_radius as f32 * ((n as f32 * phi_lamps).sin())) as i32, + ); + let lamp_var = (RandomField::new(0).get(lamp_pos.with_z(base)) % 8) as i32; + painter + .aabb(Aabb { + min: lamp_pos.with_z(base + height - 8 - lamp_var), + max: (lamp_pos + 1).with_z(base + height), + }) + .fill(chain.clone()); + painter + .aabb(Aabb { + min: lamp_pos.with_z(base + height - 9 - lamp_var), + max: (lamp_pos + 1).with_z(base + height - 8 - lamp_var), + }) + .fill(lantern.clone()); + } + } + } +}