diff --git a/assets/world/style/colors.ron b/assets/world/style/colors.ron index 0024da6795..6aa2d6c52e 100644 --- a/assets/world/style/colors.ron +++ b/assets/world/style/colors.ron @@ -76,94 +76,5 @@ lava: (184, 39, 0), vein: (61, 229, 198), ), - site: ( - castle: (), - dungeon: ( - stone: (150, 150, 175), - ), - settlement: ( - building: ( - archetype: ( - keep: ( - brick_base: (80, 80, 80), - floor_base: (80, 60, 10), - pole: (90, 70, 50), - flag: ( - Evil: (80, 10, 130), - Good: (150, 20, 0), - ), - stone: ( - Evil: (65, 60, 55), - Good: (70, 75, 80), - ), - ), - house: ( - foundation: (70, 70, 70), - floor: (100, 75, 50), - roof: ( - Roof1: (0x99, 0x5E, 0x54), - Roof2: (0x43, 0x63, 0x64), - Roof3: (0x76, 0x6D, 0x68), - Roof4: (0x55, 0x25, 0x41), - Roof5: (0x52, 0x20, 0x20), - Roof6: (0x05, 0x3A, 0x40), - Roof7: (0xCC, 0x56, 0x3E), - Roof8: (0x05, 0x48, 0x40), - // (0x1D, 0x4D, 0x45), - // (0xB3, 0x7D, 0x60), - // (0xAC, 0x5D, 0x26), - // (0x32, 0x46, 0x6B), - // (0x2B, 0x19, 0x0F), - // (0x93, 0x78, 0x51), - // (0x92, 0x57, 0x24), - // (0x4A, 0x4E, 0x4E), - // (0x2F, 0x32, 0x47), - // (0x8F, 0x35, 0x43), - // (0x6D, 0x1E, 0x3A), - // (0x6D, 0xA7, 0x80), - // (0x4F, 0xA0, 0x95), - // (0xE2, 0xB9, 0x99), - // (0x7A, 0x30, 0x22), - // (0x4A, 0x06, 0x08), - // (0x8E, 0xB4, 0x57), - ), - wall: ( - Wall1: (200, 180, 150), - Wall2: (0xB8, 0xB4, 0xA4), - Wall3: (0x76, 0x6D, 0x68), - Wall4: (0xF3, 0xC9, 0x8F), - Wall5: (0xD3, 0xB7, 0x99), - Wall6: (0xE1, 0xAB, 0x91), - Wall7: (0x82, 0x57, 0x4C), - Wall8: (0xB9, 0x96, 0x77), - Wall9: (0xAE, 0x8D, 0x9C), - ), - support: ( - Support1: (65, 30, 0), - Support2: (0x35, 0x25, 0x26), - Support3: (0x53, 0x33, 0x13), - Support4: (0x65, 0x30, 0), - ), - ), - ), - ), - plot_town_path: (80, 40, 20), - - plot_field_dirt: (55, 20, 5), - plot_field_mound: (40, 60, 10), - - wall_low: (130, 100, 0), - wall_high :(90, 70, 50), - - tower_color: (50, 50, 50), - - // NOTE: Ideally these would be part of a make_case_elim, but we can't use it beacuse - // it doesn't support struct variants yet. - plot_dirt: (90, 70, 50), - plot_grass: (100, 200, 0), - plot_water: (100, 150, 250), - plot_town: (80, 40, 20), - // TODO: Add field furrow stuff. - ), - ), + site: (), ) diff --git a/common/src/grid.rs b/common/src/grid.rs index 54a7a8f01c..d5e3b254c8 100644 --- a/common/src/grid.rs +++ b/common/src/grid.rs @@ -8,6 +8,14 @@ pub struct Grid { size: Vec2, // TODO: use u32 } +pub trait RowGen<'a>/*<'a, 'b: 'a>*/ { + type Row<'b> where Self: 'b; + type Col; + + fn row_gen<'b>(&'b mut self, yoff: i32) -> Self::Row<'b>; + fn col_gen<'b>(row: &mut Self::Row<'b>, xoff: i32) -> Self::Col where Self: 'b; +} + impl Grid { pub fn from_raw(size: Vec2, raw: impl Into>) -> Self { let cells = raw.into(); @@ -26,18 +34,24 @@ impl Grid { } #[inline] - pub fn populate_by_row Col, Col: FnMut(i32) -> T, const X: u32, const Y: u32>( - mut row: Row, + pub fn populate_by_row<'c, Gen/*, Row*//*, Col*/, const X: u32, const Y: u32>( + gen: &mut Gen, + /* mut row_gen: Row, */ + /* mut col_gen: Col, */ ) -> Self where T: Clone + Default, + /* for<'a> Gen: RowGen<'a, 'c>, */ + Gen: RowGen<'c, Col=T>, + /* for<'a> Row: FnMut(&'a mut Gen, i32) -> /*<'a, 'c>*/>::Row<'a>, */ + // for<'a, 'b> Col: FnMut(&'b mut /*<'a, 'c>*/>::Row<'a>, i32) -> T, [(); {X as usize}]: { let mut cells = vec![T::default(); {X as usize * Y as usize}]; cells.array_chunks_mut::<{X as usize}>().enumerate().for_each(|(y, cells)| { - let mut col = row(y as i32); + let mut row = gen.row_gen(y as i32); cells.iter_mut().enumerate().for_each(|(x, cell)| { - *cell = col(x as i32); + *cell = Gen::col_gen(&mut row, x as i32); }); }); Self { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 8a661ca527..58be0cd7f7 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -62,7 +62,7 @@ use specs::{ use std::{str::FromStr, sync::Arc}; use vek::*; use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement}; -use world::util::Sampler; +use world::util::SamplerMut; use common::comp::Alignment; use tracing::{error, info, warn}; @@ -2722,7 +2722,7 @@ fn handle_debug_column( }; let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let msg_generator = |/*calendar*/| { - let sampler = server.world.sample_columns(chunk_pos, server.index.as_index_ref())?; + let mut sampler = server.world.sample_columns(chunk_pos, server.index.as_index_ref())?; let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?; let basement = sim.get_interpolated(wpos, |chunk| chunk.basement)?; let water_alt = sim.get_interpolated(wpos, |chunk| chunk.water_alt)?; diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 0cdf0b45e8..15b1efe1a3 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -150,6 +150,114 @@ impl Entity { } } + fn merchant_loadout( + loadout_builder: LoadoutBuilder, + economy: Option<&trade::SiteInformation>, + ) -> LoadoutBuilder { + use common::comp::{ + inventory::{ + slot::ArmorSlot, + trade_pricing::TradePricing, + }, + Item, + }; + use hashbrown::HashMap; + use trade::Good; + + fn sort_wares(bag: &mut [Item]) { + use common::comp::item::TagExampleInfo; + + bag.sort_by(|a, b| { + a.quality() + .cmp(&b.quality()) + // sort by kind + .then( + Ord::cmp( + a.tags().first().map_or("", |tag| tag.name()), + b.tags().first().map_or("", |tag| tag.name()), + ) + ) + // sort by name + .then(Ord::cmp(&a.name(), &b.name())) + }); + } + + fn transfer(wares: &mut Vec, bag: &mut Item) { + let capacity = bag.slots().len(); + for (s, w) in bag + .slots_mut() + .iter_mut() + .zip(wares.drain(0..wares.len().min(capacity))) + { + *s = Some(w); + } + } + + let rng = &mut rand::thread_rng(); + + let mut backpack = Item::new_from_asset_expect("common.items.armor.misc.back.backpack"); + let mut bag1 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); + let mut bag2 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); + let mut bag3 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); + let mut bag4 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); + let slots = backpack.slots().len() + 4 * bag1.slots().len(); + let mut stockmap: HashMap = economy + .map(|e| e.unconsumed_stock.clone()) + .unwrap_or_default(); + // modify stock for better gameplay + + // TODO: currently econsim spends all its food on population, resulting in none + // for the players to buy; the `.max` is temporary to ensure that there's some + // food for sale at every site, to be used until we have some solution like NPC + // houses as a limit on econsim population growth + stockmap + .entry(Good::Food) + .and_modify(|e| *e = e.max(10_000.0)) + .or_insert(10_000.0); + // Reduce amount of potions so merchants do not oversupply potions. + // TODO: Maybe remove when merchants and their inventories are rtsim? + // Note: Likely without effect now that potions are counted as food + stockmap + .entry(Good::Potions) + .and_modify(|e| *e = e.powf(0.25)); + // It's safe to truncate here, because coins clamped to 3000 max + // also we don't really want negative values here + stockmap + .entry(Good::Coin) + .and_modify(|e| *e = e.min(rng.gen_range(1000.0..3000.0))); + // assume roughly 10 merchants sharing a town's stock (other logic for coins) + stockmap + .iter_mut() + .filter(|(good, _amount)| **good != Good::Coin) + .for_each(|(_good, amount)| *amount *= 0.1); + // Fill bags with stuff according to unclaimed stock + let mut wares: Vec = + TradePricing::random_items(&mut stockmap, slots as u32, true, true, 16) + .iter() + .map(|(n, a)| { + let mut i = Item::new_from_asset_expect(n); + i.set_amount(*a) + .map_err(|_| tracing::error!("merchant loadout amount failure")) + .ok(); + i + }) + .collect(); + sort_wares(&mut wares); + transfer(&mut wares, &mut backpack); + transfer(&mut wares, &mut bag1); + transfer(&mut wares, &mut bag2); + transfer(&mut wares, &mut bag3); + transfer(&mut wares, &mut bag4); + + loadout_builder + .with_asset_expect("common.loadout.village.merchant", rng) + .back(Some(backpack)) + .bag(ArmorSlot::Bag1, Some(bag1)) + .bag(ArmorSlot::Bag2, Some(bag2)) + .bag(ArmorSlot::Bag3, Some(bag3)) + .bag(ArmorSlot::Bag4, Some(bag4)) + } + /// Escape hatch for runtime creation of loadout not covered by entity /// config. // NOTE: Signature is part of interface of EntityInfo @@ -159,7 +267,7 @@ impl Entity { let kind = self.kind; if let RtSimEntityKind::Merchant = kind { - |l, trade| l.with_creator(world::site::settlement::merchant_loadout, trade) + |l, trade| l.with_creator(/*world::site::settlement*/Self::merchant_loadout, trade) } else { |l, _| l } @@ -174,7 +282,7 @@ impl Entity { .civs() .sites .iter() - .filter(|s| s.1.is_settlement() || s.1.is_castle()) + .filter(|s| s.1.is_settlement()/* || s.1.is_castle() */) .min_by_key(|(_, site)| { let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| { e * sz as i32 + sz as i32 / 2 @@ -332,7 +440,7 @@ impl Entity { .civs() .sites .iter() - .filter(|s| s.1.is_settlement() | s.1.is_castle()) + .filter(|s| s.1.is_settlement()/* | s.1.is_castle() */) .filter(|_| thread_rng().gen_range(0i32..4) == 0) .min_by_key(|(_, site)| { let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| { diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index 04e1d881c7..a6e5f73cad 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -547,42 +547,44 @@ impl<'a> Widget for Map<'a> { .color(TEXT_COLOR) .set(state.ids.show_towns_text, ui); // Castles - Image::new(self.imgs.mmap_site_castle) - .down_from(state.ids.show_towns_img, 10.0) - .w_h(20.0, 20.0) - .set(state.ids.show_castles_img, ui); - if Button::image(if show_castles { - self.imgs.checkbox_checked - } else { - self.imgs.checkbox - }) - .w_h(18.0, 18.0) - .hover_image(if show_castles { - self.imgs.checkbox_checked_mo - } else { - self.imgs.checkbox_mo - }) - .press_image(if show_castles { - self.imgs.checkbox_checked - } else { - self.imgs.checkbox_press - }) - .right_from(state.ids.show_castles_img, 10.0) - .set(state.ids.show_castles_box, ui) - .was_clicked() - { - events.push(Event::SettingsChange(MapShowCastles(!show_castles))); + if false { + Image::new(self.imgs.mmap_site_castle) + .down_from(state.ids.show_towns_img, 10.0) + .w_h(20.0, 20.0) + .set(state.ids.show_castles_img, ui); + if Button::image(if show_castles { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox + }) + .w_h(18.0, 18.0) + .hover_image(if show_castles { + self.imgs.checkbox_checked_mo + } else { + self.imgs.checkbox_mo + }) + .press_image(if show_castles { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox_press + }) + .right_from(state.ids.show_castles_img, 10.0) + .set(state.ids.show_castles_box, ui) + .was_clicked() + { + events.push(Event::SettingsChange(MapShowCastles(!show_castles))); + } + Text::new(i18n.get("hud.map.castles")) + .right_from(state.ids.show_castles_box, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.show_castles_box) + .color(TEXT_COLOR) + .set(state.ids.show_castles_text, ui); } - Text::new(i18n.get("hud.map.castles")) - .right_from(state.ids.show_castles_box, 10.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .graphics_for(state.ids.show_castles_box) - .color(TEXT_COLOR) - .set(state.ids.show_castles_text, ui); // Dungeons Image::new(self.imgs.mmap_site_dungeon) - .down_from(state.ids.show_castles_img, 10.0) + .down_from(state.ids.show_towns_img, 10.0) .w_h(20.0, 20.0) .set(state.ids.show_dungeons_img, ui); if Button::image(if show_dungeons { diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 06f4e84ae6..7779a8e367 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -117,7 +117,7 @@ impl<'a> BlockGen<'a> { } else { Some(Block::new(BlockKind::Earth, col)) } - } else if wposf.z as i32 <= alt as i32 { + } else if wposf.z as i32 <= alt as i32/* && wposf.z as f32 >= water_level */{ let grass_factor = (wposf.z as f32 - (alt - grass_depth)) .div(grass_depth) .sqrt(); @@ -134,6 +134,7 @@ impl<'a> BlockGen<'a> { }/* else if snow_cover { //if temp < CONFIG.snow_temp + 0.031 { Block::new(BlockKind::Snow, col.map(|e| (e * 255.0) as u8)) + //} }*/ else { Block::new(BlockKind::Grass, col.map(|e| (e * 255.0) as u8)) } diff --git a/world/src/canvas.rs b/world/src/canvas.rs index 47da34ef7f..6d6cf1dac2 100644 --- a/world/src/canvas.rs +++ b/world/src/canvas.rs @@ -6,7 +6,7 @@ use crate::{ land::Land, layer::spot::Spot, sim::{SimChunk, WorldSim}, - util::{Grid, Sampler}, + util::{Grid, SamplerMut}, TerrainGrid, }; use common::{ @@ -71,7 +71,7 @@ impl<'a> CanvasInfo<'a> { pub fn col_or_gen(&self, wpos: Vec2) -> Option> { self.col_inner(wpos).map(Cow::Borrowed).or_else(|| { let chunk_pos = TerrainGrid::chunk_key(wpos); - let column_gen = ColumnGen::new(self.chunks(), chunk_pos, self.index())?; + let mut column_gen = ColumnGen::new(self.chunks(), chunk_pos, self.index())?; Some(Cow::Owned(column_gen.get(( wpos/* , diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index bfe0d401a7..1ca1f6cbd3 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -5,7 +5,7 @@ mod econ; use crate::{ config::CONFIG, sim::WorldSim, - site::{namegen::NameGen, Castle, /*Settlement, */Site as WorldSite/*, Tree */}, + site::{namegen::NameGen, /*Castle, *//*Settlement, */Site as WorldSite/*, Tree */}, site2, util::{attempt, seed_expan, DHashMap, NEIGHBORS}, Index, Land, @@ -117,7 +117,8 @@ impl Civs { for _ in 0..initial_civ_count * 3 { attempt(5, || { let (kind, avoid) = match ctx.rng.gen_range(0..64) { - 0..=5 => (SiteKind::Castle, (&castle_enemies, 40)), + /* 0..=5 => (SiteKind::Castle, (&castle_enemies, 40)), */ + // 0..=5 => (SiteKind::Citadel, (&castle_enemies, 20)), 28..=31 => { /*if index.features().site2_giant_trees */{ (SiteKind::GiantTree, (&tree_enemies, 40)) @@ -126,17 +127,16 @@ impl Civs { }*/ }, 32..=37 => (SiteKind::Gnarling, (&gnarling_enemies, 40)), - // 32..=37 => (SiteKind::Citadel, 5, (&castle_enemies, 20)), _ => (SiteKind::Dungeon, (&dungeon_enemies, 40)), }; let loc = find_site_loc(&mut ctx, avoid, kind)?; match kind { - SiteKind::Castle => { + /* SiteKind::Castle => { gnarling_enemies.push(loc); dungeon_enemies.push(loc); tree_enemies.push(loc); castle_enemies.push(loc); - }, + }, */ SiteKind::Gnarling => { castle_enemies.push(loc); dungeon_enemies.push(loc); @@ -168,7 +168,7 @@ impl Civs { let (radius, flatten_radius) = match &site.kind { /* SiteKind::Settlement => (32i32, 10.0f32), */ SiteKind::Dungeon => (8i32, 3.0f32), - SiteKind::Castle => (16i32, 5.0), + /* SiteKind::Castle => (16i32, 5.0), */ SiteKind::Refactor => (32i32, 10.0), SiteKind::CliffTown => (32i32, 10.0), /* SiteKind::Tree => (12i32, 8.0), */ @@ -177,9 +177,9 @@ impl Civs { SiteKind::Citadel => (16i32, 0.0), }; - let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind { + /* let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind { /* SiteKind::Settlement => (10.0, 6, true), */ - SiteKind::Castle => (0.0, 6, true), + /* SiteKind::Castle => (0.0, 6, true), */ _ => (0.0, 0, false), }; @@ -220,7 +220,7 @@ impl Civs { } }); } - } + } */ } // Place sites in world @@ -243,9 +243,9 @@ impl Civs { &mut rng, wpos, )), - SiteKind::Castle => { + /* SiteKind::Castle => { WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng)) - }, + }, */ SiteKind::Refactor => WorldSite::refactor(site2::Site::generate_city( &Land::from_sim(ctx.sim), &mut rng, @@ -976,7 +976,7 @@ impl Civs { SiteKind::Refactor /* | SiteKind::Settlement */ | SiteKind::CliffTown - | SiteKind::Castle + /* | SiteKind::Castle */ ) }) .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) @@ -984,7 +984,7 @@ impl Civs { .collect::>(); nearby.sort_by_key(|(_, dist)| *dist as i32); - if let SiteKind::Refactor | /* SiteKind::Settlement | */SiteKind::CliffTown | SiteKind::Castle = + if let SiteKind::Refactor | /* SiteKind::Settlement | */SiteKind::CliffTown/* | SiteKind::Castle*/ = self.sites[site].kind { for (nearby, _) in nearby.into_iter().take(5) { @@ -1234,7 +1234,7 @@ impl fmt::Display for Site { pub enum SiteKind { /* Settlement, */ Dungeon, - Castle, + /* Castle, */ Refactor, CliffTown, /* Tree, */ @@ -1354,7 +1354,7 @@ impl SiteKind { && chunk.near_cliffs() && suitable_for_town(4.0) }, - SiteKind::Castle => { + /* SiteKind::Castle => { if chunk.tree_density > 0.4 || chunk.river.near_water() || chunk.near_cliffs() { return false; } @@ -1380,7 +1380,7 @@ impl SiteKind { } } true - }, + }, */ SiteKind::Refactor/* | SiteKind::Settlement*/ => suitable_for_town(6.7), _ => true, } @@ -1405,7 +1405,7 @@ impl Site { matches!(self.kind, /*SiteKind::Settlement | */SiteKind::Refactor) } - pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) } + /* pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) } */ } #[derive(PartialEq, Debug, Clone)] diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index ec54c0fdd5..327d87bf96 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -2,7 +2,7 @@ use crate::{ all::ForestKind, sim::{local_cells, Cave, Path, RiverData, RiverKind, SimChunk, WorldSim}, site::SpawnRules, - util::{RandomField, Sampler}, + util::{RandomField, RowGen, Sampler, SamplerMut}, IndexRef, CONFIG, }; use common::{ @@ -25,7 +25,7 @@ use tracing::error; use vek::*; pub struct ColumnGen1D<'a, 'b> { - pub(crate) parent: &'b ColumnGen<'a>, + pub(crate) parent: &'b mut ColumnGen<'a>, wpos_row: i32, wposy: f32, cubic_y: Cubic, @@ -80,8 +80,19 @@ pub struct ColumnGen<'a> { pub(crate) neighbor_river_data: Vec<(Vec2, &'a SimChunk, &'a RiverData)>, pub(crate) homogeneous_water_level: Option>, // pub(crate) spawn_rules: SpawnRules, + + /// Scratch data + river_scratch: /*[RiverScratchData<'a>; 7 * 7]*/Vec>, } +type RiverScratchData<'a> = + ( + Vec2, + &'a SimChunk, + &'a RiverData, + Option<(Vec2, Vec2, f64, (f64, (Vec2, Vec3>), &'a SimChunk))>, + ); + #[derive(Deserialize)] pub struct Colors { pub cold_grass: (f32, f32, f32), @@ -534,20 +545,43 @@ impl<'a> ColumnGen<'a> { .fold(SpawnRules::default(), |a, b| a.combine(b)); */ let my_chunk_idx = vec2_as_uniform_idx(sim.map_size_lg(), chunk_pos); + let mut homogeneous_water_level = true; + let river_kind = sim_chunk.river.river_kind; let neighbor_river_data = local_cells(sim.map_size_lg(), my_chunk_idx).filter_map(|neighbor_idx: usize| { let neighbor_pos = uniform_idx_as_vec2(sim.map_size_lg(), neighbor_idx); let neighbor_chunk = sim.get(neighbor_pos)?; + match (neighbor_chunk.river.river_kind, river_kind) { + (None, None) => {}, + /* (_, Some(RiverKind::Ocean)) => {}, */ + /* (_, Some(RiverKind::Lake { .. })) => { + return None; + }, */ + (_, Some(RiverKind::River { .. })) | (Some(RiverKind::River { .. }), _) => { + homogeneous_water_level = false; + }, + (_, _) if (2 * (neighbor_pos - chunk_pos) - 1).magnitude_squared() > 18/*(neighbor_pos - chunk_pos).map(i32::abs).reduce_max() > 2*/ => { + return None; + }, + /* (Some(RiverKind::Ocean), Some(RiverKind::Ocean)) => {}, */ + (Some(RiverKind::Lake { .. } | RiverKind::Ocean), Some(RiverKind::Lake { .. } | RiverKind::Ocean)) if neighbor_chunk.water_alt == sim_chunk.water_alt => {}, + (_, _) => { + homogeneous_water_level = false; + } + } + /* if /*(neighbor_pos - chunk_pos).map(i32::abs).reduce_max() <= 1 &&*/ + !(neighbor_chunk.river.river_kind.is_some() == river_kind.is_some() && neighbor_chunk.water_alt == sim_chunk.water_alt) { + homogeneous_water_level = false; + } */ neighbor_chunk.river.river_kind.and( Some((neighbor_pos, neighbor_chunk, &neighbor_chunk.river)) ) }).collect::>(); - let river_kind = sim_chunk.river.river_kind; - let homogeneous_water_level = neighbor_river_data + /* let homogeneous_water_level = neighbor_river_data .iter() .filter(|(pos, _, _)| (pos - chunk_pos).map(i32::abs).reduce_max() <= 1) .all(|(pos, chunk, river)| river.river_kind == river_kind || - chunk.water_alt == sim_chunk.water_alt); + chunk.water_alt == sim_chunk.water_alt); */ let base_sea_level = CONFIG.sea_level - 1.0 + 0.01; let homogeneous_water_level = if homogeneous_water_level { match river_kind { @@ -585,20 +619,20 @@ impl<'a> ColumnGen<'a> { sim_chunk, catmull_rom_gen, + river_scratch: Vec::with_capacity(neighbor_river_data.len()), neighbor_river_data, homogeneous_water_level, // spawn_rules, }) } - pub fn eval_at_row<'b>(&'b self, wpos_row: i32) -> ColumnGen1D<'a, 'b> { + pub fn eval_at_row<'b>(&'b mut self, wpos_row: i32) -> ColumnGen1D<'a, 'b> { let rel_chunk_pos = wpos_row - self.chunk_pos.y * TerrainChunkSize::RECT_SIZE.y as i32; // let rel_chunk_pos = wpos_row - chunk_pos.y * TerrainChunkSize::RECT_SIZE.as_::(); let wposy = rel_chunk_pos/*.y*/ as f32 / TerrainChunkSize::RECT_SIZE.y as f32; let cubic_y = cubic(wposy); ColumnGen1D { - parent: self, wpos_row, wposy, cubic_y, @@ -620,25 +654,27 @@ impl<'a> ColumnGen<'a> { surface_veg_spline: self.surface_veg_spline.eval_at_row(cubic_y), cliff_height_spline: self.cliff_height_spline.eval_at_row(cubic_y), /* spawn_rules, */ + + parent: self, } } } -impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { +impl<'a, 'b, 'c> SamplerMut<'a, 'c> for ColumnGen1D<'a, 'b> { type Index = /*(Vec2, IndexRef<'a>, Option<&'a Calendar>)*//*Vec2*/i32; type Sample = /*Option<*/ColumnSample/*<'a>*//*>*/; /// Precondition: wpos should be inside the chunk associated with self.chunk_pos. - fn get(&self, /*(wpos, index, calendar)*/wpos_col: Self::Index) -> ColumnSample/*<'a>*/ { - let &Self { - parent, + fn get(&'c mut self, /*(wpos, index, calendar)*/wpos_col: Self::Index) -> ColumnSample/*<'a>*/ { + let &mut Self { + parent: ref mut parent, wpos_row, wposy, cubic_y, .. } = self; - let &ColumnGen { + let &mut ColumnGen { sim, chunk_pos, index, @@ -646,8 +682,9 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { sim_chunk, homogeneous_water_level, ref neighbor_river_data, + ref mut river_scratch, .. - } = parent; + } = *parent; let wpos = Vec2::new(wpos_col, wpos_row); let wposf = wpos.map(|e| e as f64); @@ -728,7 +765,11 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { }; let small_nz = |wposf: Vec2| { - sim.gen_ctx.small_nz.get(wposf.into_array()) as f32 + sim.gen_ctx.fast_small_nz.get(wposf.into_array()) as f32 + // 0.0f32 + }; + let small_nz_f64 = |wposf: Vec2| { + sim.gen_ctx.fast_small_nz.get(wposf.into_array())/* as f64*/ // 0.0f32 }; @@ -754,11 +795,15 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { let riverless_alt = alt; let (alt, water_level, water_dist, water_dist_) = if let Some(water_level) = homogeneous_water_level { - (alt, water_level.unwrap_or(base_sea_level), water_level.and(Some(0.0)), None::) + (water_level.map(|water_level| alt.min(water_level - 1.0)).unwrap_or(alt), water_level.unwrap_or(base_sea_level), water_level.and(Some(0.0)), /*None::*/water_level.and(Some(0.0))) } else { - (alt, base_sea_level, None::, None::) - /* let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * 2.0f64.sqrt()) + 6.0; - let neighbor_river_data = neighbor_river_data + /* let mut lake_inner = || */{ + /* return (alt, base_sea_level, None::, None::); */ + let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * 2.0f64.sqrt()) + 6.0; + let lake_width_noise = small_nz_f64((wposf.map(|e| e as f64).div(32.0))); + river_scratch.clear(); + river_scratch.extend( + /*let neighbor_river_data = */neighbor_river_data .iter() .copied() .map(|(posj, chunkj, river)| { @@ -786,7 +831,7 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { let downhill_pos = downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { e.div_euclid(sz as i32) }); - let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef + neighbor_coef * 0.5; + let neighbor_wpos = posj.map(|e| e as f64) * neighbor_coef/* + neighbor_coef * 0.5*/; let direction = neighbor_wpos - downhill_wpos; let river_width_min = if let RiverKind::River { cross_section } = kind { cross_section.x as f64 @@ -858,7 +903,7 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { return (posj, chunkj, river, None); } let neighbor_pass_wpos = - neighbor_pass_pos.map(|e| e as f64) + neighbor_coef * 0.5; + neighbor_pass_pos.map(|e| e as f64)/* + neighbor_coef * 0.5*/; let neighbor_pass_pos = neighbor_pass_pos .map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); let coeffs = river_spline_coeffs( @@ -874,13 +919,9 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { Some(RiverKind::Lake { .. } | RiverKind::Ocean) ) { let water_chunk = posj.map(|e| e as f64); - let lake_width_noise = sim - .gen_ctx - .small_nz - .get((wposf.map(|e| e as f64).div(32.0)).into_array()); let water_aabr = Aabr { - min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0, - max: (water_chunk + 1.0) * neighbor_coef - 4.0 + min: (water_chunk - 0.5) * neighbor_coef + 4.0 - lake_width_noise * 8.0, + max: (water_chunk + 0.5) * neighbor_coef - 4.0 + lake_width_noise * 8.0, }; let pos = water_aabr.projected_point(wposf); @@ -925,13 +966,9 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { }, RiverKind::Ocean => { let water_chunk = posj.map(|e| e as f64); - let lake_width_noise = sim - .gen_ctx - .small_nz - .get((wposf.map(|e| e as f64).div(32.0)).into_array()); let water_aabr = Aabr { - min: water_chunk * neighbor_coef + 4.0 - lake_width_noise * 8.0, - max: (water_chunk + 1.0) * neighbor_coef - 4.0 + lake_width_noise * 8.0, + min: (water_chunk - 0.5) * neighbor_coef + 4.0 - lake_width_noise * 8.0, + max: (water_chunk + 0.5) * neighbor_coef - 4.0 + lake_width_noise * 8.0, }; let pos = water_aabr.projected_point(wposf); ( @@ -958,7 +995,7 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { lake_width * 0.5 }; let river_width_noise = - (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array())) + (small_nz_f64(river_pos.div(16.0))) .max(-1.0) .min(1.0) .mul(0.5) @@ -989,7 +1026,8 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { )), ) }) - .collect::>(); + /*.collect::>()*/); + let neighbor_river_data = &*river_scratch; debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level); @@ -1277,7 +1315,7 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { // has been carefully designed to handle innumeral edge cases. Please // test any changes to this code extremely well to avoid regressions: some // edge cases are very rare indeed! - let alt = neighbor_river_data.into_iter().fold( + let alt = neighbor_river_data.into_iter().copied().fold( WeightedSum::default().with(riverless_alt, 1.0), |alt, (river_chunk_idx, river_chunk, river, dist_info)| match ( river.river_kind, @@ -1429,7 +1467,9 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { f32::MIN }); - (alt, water_level, water_dist) */ + (alt, water_level, water_dist, water_dist) + }/*; + lake_inner()*/ }; let riverless_alt_delta = small_nz(wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))) @@ -1464,7 +1504,7 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { let cliff_offset = cliff * cliff_height; let riverless_alt_delta = riverless_alt_delta + (cliff - 0.5) * cliff_height; - let warp_factor = water_dist_.map_or(1.0, |d| ((d - 0.0) / 64.0).clamped(0.0, 1.0)); + let warp_factor = water_dist_.map_or(1.0, |d| ((d - 0.0) / 19.0).clamped(0.0, 1.0)); // NOTE: To disable warp, uncomment this line. // let warp_factor = 0.0; @@ -1832,17 +1872,32 @@ impl<'a, 'b> Sampler<'a, 'b> for ColumnGen1D<'a, 'b> { } } -impl<'a, 'b> Sampler<'a, 'b> for ColumnGen<'a> { +impl<'a, 'b> SamplerMut<'a, 'b> for ColumnGen<'a> { type Index = /*(Vec2, IndexRef<'a>, Option<&'a Calendar>)*/Vec2; type Sample = ColumnSample/*<'a>*/; /// Precondition: wpos should be inside the chunk associated with self.chunk_pos. - fn get(&self, /*(wpos, index, calendar)*/wpos: Self::Index) -> ColumnSample/*<'a>*/ { - let col = self.eval_at_row(wpos.y); + fn get(&'b mut self, /*(wpos, index, calendar)*/wpos: Self::Index) -> ColumnSample/*<'a>*/ { + let mut col = self.eval_at_row(wpos.y); col.get(wpos.x) } } +impl<'a/*: 'b, 'b*/> RowGen<'a> for ColumnGen<'a> { + type Row<'b> = ColumnGen1D<'a, 'b> where Self: 'b; + type Col = ColumnSample; + + #[inline(always)] + fn row_gen<'b>(&'b mut self, offs_y: i32) -> Self::Row<'b> { + self.eval_at_row(/* - grid_border*/offs_y + self.chunk_pos.y * TerrainChunkSize::RECT_SIZE.y as i32) + } + + #[inline(always)] + fn col_gen<'b>(row: &mut Self::Row<'b>, offs_x: i32) -> Self::Col where Self: 'b { + row.get(/* - grid_border + */offs_x + row.parent.chunk_pos.x * TerrainChunkSize::RECT_SIZE.y as i32) + } +} + #[derive(Clone, Default)] pub struct ColumnSample/*<'a>*/ { pub alt: f32, diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 52c5321298..5b06ecf425 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -1061,8 +1061,6 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { max: aabr.min + 1, }; */ canvas.foreach_col_area(aabr, /*Aabr { min: canvas.wpos(), max: canvas.wpos() + 1 }, */|canvas, wpos2d, col| { - let underwater = col.water_level.floor() > col.alt; - let /*kind*/(kind, water_mode) = /* scatter.iter().enumerate().find_map( |( i, @@ -1099,7 +1097,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) { .unwrap_or(density); */ if density > 0.0 && rng.gen::() < density //RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) - && matches!(&water_mode, Underwater | Floating) == underwater + && matches!(&water_mode, Underwater | Floating) == { col.water_level.floor() > col.alt } { let block_kind = canvas .get(Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32)) diff --git a/world/src/layer/spot.rs b/world/src/layer/spot.rs index 70bffb8ee4..cfb533a345 100644 --- a/world/src/layer/spot.rs +++ b/world/src/layer/spot.rs @@ -558,7 +558,7 @@ pub fn apply_spots_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) { }; // Blit base structure if let Some(base_structures) = spot_config.base_structures { - let structures = Structure::load_group(base_structures).read(); + let structures = Structure::load_group(base_structures).get(); let structure = structures.choose(&mut rng).unwrap(); let origin = spot_wpos2d.with_z( canvas diff --git a/world/src/lib.rs b/world/src/lib.rs index aff026e1d7..581f1e7cb8 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -11,6 +11,7 @@ arbitrary_enum_discriminant, associated_const_equality, bool_to_option, + generic_associated_types, label_break_value, option_zip, portable_simd, @@ -52,7 +53,7 @@ use crate::{ index::Index, layer::spot::Spot, site::{SiteKind, SpawnRules}, - util::{Grid, Sampler}, + util::{Grid, Sampler, SamplerMut}, }; use common::{ assets, @@ -162,7 +163,7 @@ impl World { _ => 0, }, }, - civ::SiteKind::Castle => world_msg::SiteKind::Castle, + /* civ::SiteKind::Castle => world_msg::SiteKind::Castle, */ /* civ::SiteKind::Tree | */civ::SiteKind::GiantTree => world_msg::SiteKind::Tree, // TODO: Maybe change? civ::SiteKind::Gnarling | civ::SiteKind::Citadel => world_msg::SiteKind::Gnarling, @@ -198,7 +199,7 @@ impl World { chunk_pos: Vec2, index: IndexRef<'a>, /* calendar: Option<&'_ Calendar>, */ - ) -> Option Sampler<'a, 'b, + ) -> Option SamplerMut<'a, 'b, Index = /*(Vec2, IndexRef, Option<&'_ Calendar>)*/Vec2, Sample = ColumnSample/*<'a>*/, > + 'a> { @@ -227,9 +228,9 @@ impl World { } #[allow(clippy::result_unit_err)] - pub fn generate_chunk( - &self, - index: IndexRef, + pub fn generate_chunk<'a>( + &'a self, + index: IndexRef<'a>, chunk_pos: Vec2, // TODO: misleading name mut should_continue: impl FnMut() -> bool, @@ -239,7 +240,8 @@ impl World { let calendar = self.sim.calendar.as_ref(); // FIXME: Deal with this properly if it's not okay to exit early. - let sampler = self.sample_blocks(chunk_pos, index/*, calendar*/); + /* let mut sampler = self.sample_blocks(chunk_pos, index/*, calendar*/); */ + let mut sampler = ColumnGen::new(&self.sim, chunk_pos, index/*, calendar*/).map(BlockGen::new); // dbg!(&sampler.column_gen.chaos_spline); let air = Block::air(SpriteKind::Empty); @@ -272,18 +274,45 @@ impl World { let grid_border = /*4*/0; let chunk_wpos2d = chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); let chunk_center_wpos2d = chunk_wpos2d + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2); + let column_gen = &mut sampler.column_gen; + + /* #[inline(always)] + fn constrain_<'a>(chunk_wpos2d_y: i32) -> + impl for<'b> FnMut(&'b mut ColumnGen<'a>, i32) -> crate::column::ColumnGen1D<'a, 'b> + { + move |column_gen: &mut crate::column::ColumnGen<'a>, offs_y| { + column_gen.eval_at_row(chunk_wpos2d_y/* - grid_border*/ + offs_y) + } + } */ + + /* #[inline(always)] + fn constrain<'a>(chunk_wpos2d_x: i32) -> + impl for<'b> FnMut(&mut as crate::util::RowGen<'a>>::Row<'b>, i32) -> ColumnSample + { + move |column_gen, offs_x| { + column_gen.get(chunk_wpos2d_x/* - grid_border*/ + offs_x) + } + } */ + /* fn constrain<'a, F, S, T>(f: F) -> F + where + F: for<'b, 'c> FnMut(&'c mut crate::column::ColumnGen1D<'a, 'b>, S) -> T, + { + f + } */ let zcache_grid: Grid = - Grid::populate_by_row::<_, _, {TerrainChunkSize::RECT_SIZE.x}, {TerrainChunkSize::RECT_SIZE.y}>( + Grid::populate_by_row::, /*_, */{TerrainChunkSize::RECT_SIZE.x}, {TerrainChunkSize::RECT_SIZE.y}>( /* TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, */ - |offs_y| { - let column_gen = sampler.column_gen.eval_at_row(chunk_wpos2d.y/* - grid_border*/ + offs_y); - move |offs_x| { - /* ZCache { - sample: */column_gen.get(chunk_wpos2d.x/* - grid_border*/ + offs_x)/*, - calendar: column_gen.parent.sim.calendar.as_ref(), - }*/ - } - }, + column_gen, + /*constrain_(chunk_wpos2d.y) + /*/*constrain_(*/move |column_gen: &mut crate::column::ColumnGen<'a>, offs_y| { + /*let mut column_gen = */column_gen.eval_at_row(chunk_wpos2d.y/* - grid_border*/ + offs_y) + }/*)*/*/,*/ + /* /*constrain(*/move |column_gen: &mut crate::column::ColumnGen1D<'a, '_>, offs_x| { + /* ZCache { + sample: */column_gen.get(chunk_wpos2d.x/* - grid_border*/ + offs_x)/*, + calendar: column_gen.parent.sim.calendar.as_ref(), + }*/ + }/*)*/*//*constrain(chunk_wpos2d.x),*/ /* |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs/*, index, calendar*/)/*None*/ */ ); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index aae1885133..0771ecacac 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -33,7 +33,7 @@ use crate::{ layer::spot::Spot, site::Site, util::{ - seed_expan, DHashSet, FastNoise, FastNoise2d, RandomField, Sampler, StructureGen2d, + seed_expan, DHashSet, FastNoise, FastNoise2d, RandomField, Sampler, SamplerMut, StructureGen2d, CARDINALS, LOCALITY, NEIGHBORS, }, IndexRef, CONFIG, @@ -136,6 +136,7 @@ pub(crate) struct GenCtx { pub uplift_nz: Worley, pub fast_hill_nz: Value, + pub fast_small_nz: /*FastNoise2d*/Value, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -536,6 +537,7 @@ impl WorldSim { let uplift_scale = 128.0; let uplift_turb_scale = uplift_scale / 4.0; let hill_nz_seed; + let small_nz_seed; // NOTE: Changing order will significantly change WorldGen, so try not to! let gen_ctx = GenCtx { @@ -561,7 +563,7 @@ impl WorldSim { .set_lacunarity(2.0) .set_seed(rng.gen()), - small_nz: BasicMulti::new().set_octaves(2).set_seed(rng.gen()), + small_nz: BasicMulti::new().set_octaves(2).set_seed( { small_nz_seed = rng.gen(); small_nz_seed }), rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()), tree_nz: BasicMulti::new() .set_octaves(12) @@ -572,6 +574,7 @@ impl WorldSim { structure_gen: StructureGen2d::new(rng.gen(), 24, 10), _big_structure_gen: StructureGen2d::new(rng.gen(), 768, 512), + fast_small_nz: Value::new().set_seed(small_nz_seed)/*FastNoise2d::new(small_nz_seed)*/, _region_gen: StructureGen2d::new(rng.gen(), 400, 96), humid_nz: Billow::new() .set_octaves(9) @@ -1835,7 +1838,7 @@ impl WorldSim { } } - pub fn get_base_z(&self, chunk_pos: Vec2) -> Option { + /* pub fn get_base_z(&self, chunk_pos: Vec2) -> Option { let in_bounds = chunk_pos .map2(self.map_size_lg().chunks(), |e, sz| { e > 0 && e < sz as i32 - 2 @@ -1859,7 +1862,7 @@ impl WorldSim { } }) .fold(None, |a: Option, x| a.map(|a| a.min(x)).or(Some(x))) - } + } */ pub fn get_interpolated(&self, pos: Vec2, mut f: F) -> Option where @@ -2309,7 +2312,7 @@ impl SimChunk { Some( uniform_idx_as_vec2(map_size_lg, downhill_pre as usize) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32) - + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2), + /* + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2) */, ) }; @@ -2483,7 +2486,7 @@ impl SimChunk { self.water_alt > self.alt || self.river.river_kind.is_some() } - pub fn get_base_z(&self) -> f32 { self.alt/* - self.chaos * 16.0 - self.cliff_height.max(0.0) * /*3.125.powf(1.5)*/5.625 - 4.0/*/* * 50.0 - 16.0 */- 7.5*/*/ - 6.0 } + /* pub fn get_base_z(&self) -> f32 { self.alt/* - self.chaos * 16.0 - self.cliff_height.max(0.0) * /*3.125.powf(1.5)*/5.625 - 4.0/*/* * 50.0 - 16.0 */- 7.5*/*/ - 6.0 } */ pub fn get_biome(&self) -> BiomeKind { let savannah_hum_temp = [0.05..0.55, 0.3..1.6]; diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index 907f539533..29ff6e1a1b 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -215,8 +215,8 @@ pub fn uniform_noise( pub fn local_cells(map_size_lg: MapSizeLg, posi: usize) -> impl Clone + Iterator { let pos = uniform_idx_as_vec2(map_size_lg, posi); // NOTE: want to keep this such that the chunk index is in ascending order! - let grid_size = 3i32; - let grid_bounds = 2 * grid_size + 1; + let grid_size = 2i32; + let grid_bounds = 2 * (grid_size + 1)/* + 1*/; (0..grid_bounds * grid_bounds) .into_iter() .map(move |index| { diff --git a/world/src/site/castle/mod.rs b/world/src/site/castle/mod.rs deleted file mode 100644 index ac9a79ef7e..0000000000 --- a/world/src/site/castle/mod.rs +++ /dev/null @@ -1,441 +0,0 @@ -use super::SpawnRules; -use crate::{ - column::ColumnSample, - sim::WorldSim, - site::{ - namegen::NameGen, - settlement::building::{ - archetype::keep::{Attr, FlagColor, Keep as KeepArchetype, StoneColor}, - Archetype, Ori, - }, - }, - IndexRef, -}; -use common::{ - generation::ChunkSupplement, - terrain::{Block, BlockKind, SpriteKind}, - vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, -}; -use core::f32; -use rand::prelude::*; -use serde::Deserialize; -use vek::*; - -struct Keep { - offset: Vec2, - locus: i32, - storeys: i32, - is_tower: bool, - alt: i32, -} - -struct Tower { - offset: Vec2, - alt: i32, -} - -pub struct Castle { - name: String, - origin: Vec2, - //seed: u32, - radius: i32, - towers: Vec, - keeps: Vec, - rounded_towers: bool, - ridged: bool, - flags: bool, - - evil: bool, -} - -pub struct GenCtx<'a, R: Rng> { - sim: Option<&'a mut WorldSim>, - rng: &'a mut R, -} - -#[derive(Deserialize)] -pub struct Colors; - -impl Castle { - pub fn generate(wpos: Vec2, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self { - let ctx = GenCtx { sim, rng }; - - let boundary_towers = ctx.rng.gen_range(5..10); - let keep_count = ctx.rng.gen_range(1..4); - let boundary_noise = ctx.rng.gen_range(-2i32..8).max(1) as f32; - - let radius = 150; - - let this = Self { - name: { - let name = NameGen::location(ctx.rng).generate(); - match ctx.rng.gen_range(0..6) { - 0 => format!("Fort {}", name), - 1 => format!("{} Citadel", name), - 2 => format!("{} Castle", name), - 3 => format!("{} Stronghold", name), - 4 => format!("{} Fortress", name), - _ => format!("{} Keep", name), - } - }, - origin: wpos, - // alt: ctx - // .sim - // .as_ref() - // .and_then(|sim| sim.get_alt_approx(wpos)) - // .unwrap_or(0.0) as i32 - // + 6, - //seed: ctx.rng.gen(), - radius, - - towers: (0..boundary_towers) - .map(|i| { - let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0; - let dir = Vec2::new(angle.cos(), angle.sin()); - let dist = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0; - - let mut offset = (dir * dist).map(|e| e as i32); - // Try to move the tower around until it's not intersecting a path - for i in (1..80).step_by(5) { - if ctx - .sim - .as_ref() - .and_then(|sim| sim.get_nearest_path(wpos + offset)) - .map(|(dist, _, _, _)| dist > 24.0) - .unwrap_or(true) - { - break; - } - offset = (dir * dist) - .map(|e| (e + ctx.rng.gen_range(-1.0..1.0) * i as f32) as i32); - } - - Tower { - offset, - alt: ctx - .sim - .as_ref() - .and_then(|sim| sim.get_alt_approx(wpos + offset)) - .unwrap_or(0.0) as i32 - + 2, - } - }) - .collect(), - rounded_towers: ctx.rng.gen(), - ridged: ctx.rng.gen(), - flags: ctx.rng.gen(), - evil: ctx.rng.gen(), - keeps: (0..keep_count) - .map(|i| { - let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0; - let dir = Vec2::new(angle.cos(), angle.sin()); - let dist = - (radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3; - - let locus = ctx.rng.gen_range(20..26); - let offset = (dir * dist).map(|e| e as i32); - let storeys = ctx.rng.gen_range(1..8).clamped(3, 5); - - Keep { - offset, - locus, - storeys, - is_tower: true, - alt: ctx - .sim - .as_ref() - .and_then(|sim| sim.get_alt_approx(wpos + offset)) - .unwrap_or(0.0) as i32 - + 2, - } - }) - .collect(), - }; - - this - } - - pub fn name(&self) -> &str { &self.name } - - pub fn contains_point(&self, wpos: Vec2) -> bool { - let lpos = wpos - self.origin; - for i in 0..self.towers.len() { - let tower0 = &self.towers[i]; - let tower1 = &self.towers[(i + 1) % self.towers.len()]; - - if lpos.determine_side(Vec2::zero(), tower0.offset) > 0 - && lpos.determine_side(Vec2::zero(), tower1.offset) <= 0 - && lpos.determine_side(tower0.offset, tower1.offset) > 0 - { - return true; - } - } - - false - } - - pub fn get_origin(&self) -> Vec2 { self.origin } - - pub fn radius(&self) -> f32 { 200.0 } - - pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { - SpawnRules { - trees: wpos.distance_squared(self.origin) > self.radius.pow(2), - ..SpawnRules::default() - } - } - - pub fn apply_to<'a>( - &'a self, - index: IndexRef, - wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample/*<'a>*/>, - vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), - ) { - for y in 0..vol.size_xy().y as i32 { - for x in 0..vol.size_xy().x as i32 { - let offs = Vec2::new(x, y); - - let wpos2d = wpos2d + offs; - let rpos = wpos2d - self.origin; - - if rpos.magnitude_squared() > (self.radius + 64).pow(2) { - continue; - } - - let col_sample = if let Some(col) = get_column(offs) { - col - } else { - continue; - }; - - // Inner ground - if self.contains_point(wpos2d) { - let surface_z = col_sample.alt as i32; - for z in -5..3 { - let pos = Vec3::new(offs.x, offs.y, surface_z + z); - - if z > 0 { - if vol.get(pos).unwrap().kind() != BlockKind::Water { - // TODO: Take environment into account. - let _ = vol.set(pos, Block::air(SpriteKind::Empty)); - } - } else { - let _ = vol.set( - pos, - Block::new( - BlockKind::Earth, - col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), - ), - ); - } - } - } - - let (wall_dist, wall_pos, wall_alt, wall_ori, _towers) = (0..self.towers.len()) - .map(|i| { - let tower0 = &self.towers[i]; - let tower1 = &self.towers[(i + 1) % self.towers.len()]; - - let wall = LineSegment2 { - start: tower0.offset.map(|e| e as f32), - end: tower1.offset.map(|e| e as f32), - }; - - let projected = wall - .projected_point(rpos.map(|e| e as f32)) - .map(|e| e.floor() as i32); - - let tower0_dist = tower0 - .offset - .map(|e| e as f32) - .distance(projected.map(|e| e as f32)); - let tower1_dist = tower1 - .offset - .map(|e| e as f32) - .distance(projected.map(|e| e as f32)); - let tower_lerp = tower0_dist / (tower0_dist + tower1_dist); - let wall_ori = if (tower0.offset.x - tower1.offset.x).abs() - < (tower0.offset.y - tower1.offset.y).abs() - { - Ori::North - } else { - Ori::East - }; - - ( - wall.distance_to_point(rpos.map(|e| e as f32)) as i32, - projected, - Lerp::lerp(tower0.alt as f32, tower1.alt as f32, tower_lerp) as i32, - wall_ori, - [tower0, tower1], - ) - }) - .min_by_key(|x| x.0) - .unwrap(); - let border_pos = (wall_pos - rpos).map(|e| e.abs()); - let wall_rpos = if wall_ori == Ori::East { - rpos - } else { - rpos.yx() - }; - let head_space = col_sample - .path - .map(|(dist, _, path, _)| path.head_space(dist)) - .unwrap_or(0); - - let wall_sample = /* if let Some(col) = get_column(offs + wall_pos - rpos) { - col - } else { - col_sample - }*/col_sample; - - // Make sure particularly weird terrain doesn't give us underground walls - let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0); - - let keep_archetype = KeepArchetype { - flag_color: if self.evil { - FlagColor::Evil - } else { - FlagColor::Good - }, - stone_color: if self.evil { - StoneColor::Evil - } else { - StoneColor::Good - }, - }; - - for z in -10..64 { - let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z); - - // Boundary wall - let wall_z = wpos.z - wall_alt; - if z < head_space { - continue; - } - - let mut mask = keep_archetype.draw( - index, - Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt, - wall_dist, - border_pos, - rpos - wall_pos, - wall_z, - wall_ori, - 4, - 0, - &Attr { - storeys: 2, - is_tower: false, - flag: self.flags, - ridged: false, - rounded: true, - has_doors: false, - }, - ); - - // Boundary towers - for tower in &self.towers { - let tower_wpos = Vec3::new( - self.origin.x + tower.offset.x, - self.origin.y + tower.offset.y, - tower.alt, - ); - let tower_locus = 10; - - let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs()); - mask = mask.resolve_with(keep_archetype.draw( - index, - if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() { - wpos - tower_wpos - } else { - Vec3::new( - wpos.y - tower_wpos.y, - wpos.x - tower_wpos.x, - wpos.z - tower_wpos.z, - ) - }, - border_pos.reduce_max() - tower_locus, - Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()), - (wpos - tower_wpos).xy(), - wpos.z - tower.alt, - if border_pos.x > border_pos.y { - Ori::East - } else { - Ori::North - }, - tower_locus, - 0, - &Attr { - storeys: 3, - is_tower: true, - flag: self.flags, - ridged: self.ridged, - rounded: self.rounded_towers, - has_doors: false, - }, - )); - } - - // Keeps - for keep in &self.keeps { - let keep_wpos = Vec3::new( - self.origin.x + keep.offset.x, - self.origin.y + keep.offset.y, - keep.alt, - ); - - let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs()); - mask = mask.resolve_with(keep_archetype.draw( - index, - if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() { - wpos - keep_wpos - } else { - Vec3::new( - wpos.y - keep_wpos.y, - wpos.x - keep_wpos.x, - wpos.z - keep_wpos.z, - ) - }, - border_pos.reduce_max() - keep.locus, - Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()), - (wpos - keep_wpos).xy(), - wpos.z - keep.alt, - if border_pos.x > border_pos.y { - Ori::East - } else { - Ori::North - }, - keep.locus, - 0, - &Attr { - storeys: keep.storeys, - is_tower: keep.is_tower, - flag: self.flags, - ridged: self.ridged, - rounded: self.rounded_towers, - has_doors: true, - }, - )); - } - - if let Some(block) = mask.finish() { - let _ = vol.set(Vec3::new(offs.x, offs.y, wpos.z), block); - } - } - } - } - } - - pub fn apply_supplement<'a>( - &'a self, - // NOTE: Used only for dynamic elements like chests and entities! - _dynamic_rng: &mut impl Rng, - _wpos2d: Vec2, - _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample/*<'a>*/>, - _supplement: &mut ChunkSupplement, - ) { - // TODO - } -} diff --git a/world/src/site/economy/context.rs b/world/src/site/economy/context.rs index b41dfb4d9a..f7dfedb0a9 100644 --- a/world/src/site/economy/context.rs +++ b/world/src/site/economy/context.rs @@ -113,7 +113,7 @@ impl Environment { } { - let mut castles = EconStatistics::default(); + /* let mut castles = EconStatistics::default(); */ let mut towns = EconStatistics::default(); let mut dungeons = EconStatistics::default(); for site in index.sites.ids() { @@ -123,7 +123,7 @@ impl Environment { towns += site.economy.pop }, SiteKind::Dungeon(_) => dungeons += site.economy.pop, - SiteKind::Castle(_) => castles += site.economy.pop, + /* SiteKind::Castle(_) => castles += site.economy.pop, */ /* SiteKind::Tree(_) => (), */ SiteKind::GiantTree(_) => (), SiteKind::Gnarling(_) => {}, @@ -137,14 +137,14 @@ impl Environment { towns.sum / (towns.count as f32) ); } - if castles.valid() { + /* if castles.valid() { info!( "Castles {:.0}-{:.0} avg {:.0}", castles.min, castles.max, castles.sum / (castles.count as f32) ); - } + } */ if dungeons.valid() { info!( "Dungeons {:.0}-{:.0} avg {:.0}", @@ -389,9 +389,9 @@ mod tests { crate::site::SiteKind::Dungeon(_) => { common::terrain::site::SitesKind::Dungeon }, - crate::site::SiteKind::Castle(_) => { + /* crate::site::SiteKind::Castle(_) => { common::terrain::site::SitesKind::Castle - }, + }, */ _ => common::terrain::site::SitesKind::Void, }, }; @@ -417,9 +417,9 @@ mod tests { // loading on demand using the public API. There is no way to set // the name, do we care? let mut settlement = match i.kind { - common::terrain::site::SitesKind::Castle => crate::site::Site::castle( + /* common::terrain::site::SitesKind::Castle => crate::site::Site::castle( crate::site::Castle::generate(wpos, None, &mut rng), - ), + ), */ common::terrain::site::SitesKind::Dungeon => { crate::site::Site::dungeon(crate::site2::Site::generate_dungeon( &crate::Land::empty(), diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index d2e5fc3e3e..9cc9e29fb4 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -1,13 +1,13 @@ mod block_mask; -mod castle; +// mod castle; pub mod economy; pub mod namegen; -pub mod settlement; +// pub mod settlement; // mod tree; // Reexports pub use self::{ - block_mask::BlockMask, castle::Castle, economy::Economy/* , settlement::Settlement, tree::Tree, */ + block_mask::BlockMask, /* castle::Castle, */economy::Economy/* , settlement::Settlement, tree::Tree, */ }; use crate::{column::ColumnSample, site2, Canvas}; @@ -17,11 +17,7 @@ use serde::Deserialize; use vek::*; #[derive(Deserialize)] -pub struct Colors { - pub castle: castle::Colors, - pub dungeon: site2::plot::dungeon::Colors, - pub settlement: settlement::Colors, -} +pub struct Colors; pub struct SpawnRules { pub trees: bool, @@ -62,7 +58,7 @@ pub struct Site { pub enum SiteKind { /* Settlement(Settlement), */ Dungeon(site2::Site), - Castle(Castle), + /* Castle(Castle), */ Refactor(site2::Site), CliffTown(site2::Site), /* Tree(tree::Tree), */ @@ -92,12 +88,12 @@ impl Site { } } - pub fn castle(c: Castle) -> Self { + /* pub fn castle(c: Castle) -> Self { Self { kind: SiteKind::Castle(c), economy: Economy::default(), } - } + } */ pub fn refactor(s: site2::Site) -> Self { Self { @@ -131,7 +127,7 @@ impl Site { match &self.kind { /* SiteKind::Settlement(s) => s.radius(), */ SiteKind::Dungeon(d) => d.radius(), - SiteKind::Castle(c) => c.radius(), + /* SiteKind::Castle(c) => c.radius(), */ SiteKind::Refactor(s) => s.radius(), SiteKind::CliffTown(ct) => ct.radius(), /* SiteKind::Tree(t) => t.radius(), */ @@ -144,7 +140,7 @@ impl Site { match &self.kind { /* SiteKind::Settlement(s) => s.get_origin(), */ SiteKind::Dungeon(d) => d.origin, - SiteKind::Castle(c) => c.get_origin(), + /* SiteKind::Castle(c) => c.get_origin(), */ SiteKind::Refactor(s) => s.origin, SiteKind::CliffTown(ct) => ct.origin, /* SiteKind::Tree(t) => t.origin, */ @@ -157,7 +153,7 @@ impl Site { match &self.kind { /* SiteKind::Settlement(s) => s.spawn_rules(wpos), */ SiteKind::Dungeon(d) => d.spawn_rules(wpos), - SiteKind::Castle(c) => c.spawn_rules(wpos), + /* SiteKind::Castle(c) => c.spawn_rules(wpos), */ SiteKind::Refactor(s) => s.spawn_rules(wpos), SiteKind::CliffTown(ct) => ct.spawn_rules(wpos), /* SiteKind::Tree(t) => t.spawn_rules(wpos), */ @@ -170,7 +166,7 @@ impl Site { match &self.kind { /* SiteKind::Settlement(s) => s.name(), */ SiteKind::Dungeon(d) => d.name(), - SiteKind::Castle(c) => c.name(), + /* SiteKind::Castle(c) => c.name(), */ SiteKind::Refactor(s) => s.name(), SiteKind::CliffTown(ct) => ct.name(), /* SiteKind::Tree(_) => "Giant Tree", */ @@ -196,11 +192,11 @@ impl Site { pub fn apply_to<'a>(&'a self, canvas: &mut Canvas<'a>, arena: &mut bumpalo::Bump, dynamic_rng: &mut impl Rng) { let info = canvas.info(); - let get_col = |wpos| info.col(wpos + info.wpos); + // let get_col = |wpos| info.col(wpos + info.wpos); match &self.kind { /* SiteKind::Settlement(s) => s.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk), */ SiteKind::Dungeon(d) => d.render(canvas, arena, dynamic_rng), - SiteKind::Castle(c) => c.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk), + /* SiteKind::Castle(c) => /*c.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk)*/{}, */ SiteKind::Refactor(s) => s.render(canvas, arena, dynamic_rng), SiteKind::CliffTown(ct) => ct.render(canvas, arena, dynamic_rng), /* SiteKind::Tree(t) => t.render(canvas, dynamic_rng), */ @@ -226,7 +222,7 @@ impl Site { s.apply_supplement(dynamic_rng, wpos2d, get_column, supplement, economy) }, */ SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, supplement), - SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement), + /* SiteKind::Castle(c) => /*c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement)*/{}, */ SiteKind::Refactor(_) => {}, SiteKind::CliffTown(_) => {}, /* SiteKind::Tree(_) => {}, */ diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs deleted file mode 100644 index 6b6ca5b126..0000000000 --- a/world/src/site/settlement/building/archetype/house.rs +++ /dev/null @@ -1,673 +0,0 @@ -#![allow(dead_code)] - -use super::{super::skeleton::*, Archetype}; -use crate::{ - site::BlockMask, - util::{RandomField, Sampler}, - IndexRef, -}; -use common::{ - calendar::{Calendar, CalendarEvent}, - make_case_elim, - terrain::{Block, BlockKind, SpriteKind}, -}; -use rand::prelude::*; -use serde::Deserialize; -use vek::*; - -#[derive(Deserialize)] -pub struct Colors { - pub foundation: (u8, u8, u8), - pub floor: (u8, u8, u8), - pub roof: roof_color::PureCases<(u8, u8, u8)>, - pub wall: wall_color::PureCases<(u8, u8, u8)>, - pub support: support_color::PureCases<(u8, u8, u8)>, -} - -pub struct ColorTheme { - roof: RoofColor, - wall: WallColor, - support: SupportColor, -} - -make_case_elim!( - roof_color, - #[repr(u32)] - #[derive(Clone, Copy)] - pub enum RoofColor { - Roof1 = 0, - Roof2 = 1, - Roof3 = 2, - Roof4 = 3, - Roof5 = 4, - Roof6 = 5, - Roof7 = 6, - } -); - -make_case_elim!( - wall_color, - #[repr(u32)] - #[derive(Clone, Copy)] - pub enum WallColor { - Wall1 = 0, - Wall2 = 1, - Wall3 = 2, - Wall4 = 3, - Wall5 = 4, - Wall6 = 5, - Wall7 = 6, - Wall8 = 7, - Wall9 = 8, - } -); - -make_case_elim!( - support_color, - #[repr(u32)] - #[derive(Clone, Copy)] - pub enum SupportColor { - Support1 = 0, - Support2 = 1, - Support3 = 2, - Support4 = 3, - } -); - -const ROOF_COLORS: [RoofColor; roof_color::NUM_VARIANTS] = [ - RoofColor::Roof1, - RoofColor::Roof2, - RoofColor::Roof3, - RoofColor::Roof4, - RoofColor::Roof4, - RoofColor::Roof6, - RoofColor::Roof7, -]; - -const WALL_COLORS: [WallColor; wall_color::NUM_VARIANTS] = [ - WallColor::Wall1, - WallColor::Wall2, - WallColor::Wall3, - WallColor::Wall4, - WallColor::Wall5, - WallColor::Wall6, - WallColor::Wall7, - WallColor::Wall8, - WallColor::Wall9, -]; - -const SUPPORT_COLORS: [SupportColor; support_color::NUM_VARIANTS] = [ - SupportColor::Support1, - SupportColor::Support2, - SupportColor::Support3, - SupportColor::Support4, -]; - -pub struct House { - pub colors: ColorTheme, - pub noise: RandomField, - pub roof_ribbing: bool, - pub roof_ribbing_diagonal: bool, - pub christmas_decorations: bool, -} - -#[derive(Copy, Clone)] -pub enum Pillar { - None, - Chimney(i32), - Tower(i32), -} - -#[derive(Copy, Clone)] -pub enum RoofStyle { - Hip, - Gable, - Rounded, -} - -#[derive(Copy, Clone)] -pub enum StoreyFill { - None, - Upper, - All, -} - -impl StoreyFill { - fn has_lower(&self) -> bool { matches!(self, StoreyFill::All) } - - fn has_upper(&self) -> bool { !matches!(self, StoreyFill::None) } -} - -#[derive(Copy, Clone)] -pub struct Attr { - pub central_supports: bool, - pub storey_fill: StoreyFill, - pub roof_style: RoofStyle, - pub mansard: i32, - pub pillar: Pillar, - pub levels: i32, - pub window: SpriteKind, -} - -impl Attr { - pub fn generate(rng: &mut R, _locus: i32) -> Self { - Self { - central_supports: rng.gen(), - storey_fill: match rng.gen_range(0..3) { - 0 => StoreyFill::None, - 1 => StoreyFill::Upper, - _ => StoreyFill::All, - }, - roof_style: match rng.gen_range(0..3) { - 0 => RoofStyle::Hip, - 1 => RoofStyle::Gable, - _ => RoofStyle::Rounded, - }, - mansard: rng.gen_range(-7..4).max(0), - pillar: match rng.gen_range(0..4) { - 0 => Pillar::Chimney(rng.gen_range(2..6)), - _ => Pillar::None, - }, - levels: rng.gen_range(1..3), - window: match rng.gen_range(0..4) { - 0 => SpriteKind::Window1, - 1 => SpriteKind::Window2, - 2 => SpriteKind::Window3, - _ => SpriteKind::Window4, - }, - } - } -} - -impl Archetype for House { - type Attr = Attr; - - fn generate(rng: &mut R, calendar: Option<&Calendar>) -> (Self, Skeleton) { - let len = rng.gen_range(-8..24).clamped(0, 20); - let locus = 6 + rng.gen_range(0..5); - let branches_per_side = 1 + len as usize / 20; - let levels = rng.gen_range(1..3); - let skel = Skeleton { - offset: -rng.gen_range(0..len + 7).clamped(0, len), - ori: if rng.gen() { Ori::East } else { Ori::North }, - root: Branch { - len, - attr: Attr { - storey_fill: StoreyFill::All, - mansard: 0, - pillar: match rng.gen_range(0..3) { - 0 => Pillar::Chimney(rng.gen_range(2..6)), - 1 => Pillar::Tower(5 + rng.gen_range(1..5)), - _ => Pillar::None, - }, - levels, - ..Attr::generate(rng, locus) - }, - locus, - border: 4, - children: [1, -1] - .iter() - .flat_map(|flip| (0..branches_per_side).map(move |i| (i, *flip))) - .filter_map(|(i, flip)| { - if rng.gen() { - Some(( - i as i32 * len / (branches_per_side - 1).max(1) as i32, - Branch { - len: rng.gen_range(8..16) * flip, - attr: Attr { - levels: rng.gen_range(1..4).min(levels), - ..Attr::generate(rng, locus) - }, - locus: (6 + rng.gen_range(0..3)).min(locus), - border: 4, - children: Vec::new(), - }, - )) - } else { - None - } - }) - .collect(), - }, - }; - - let this = Self { - colors: ColorTheme { - roof: *ROOF_COLORS.choose(rng).unwrap(), - wall: *WALL_COLORS.choose(rng).unwrap(), - support: *SUPPORT_COLORS.choose(rng).unwrap(), - }, - noise: RandomField::new(rng.gen()), - roof_ribbing: rng.gen(), - roof_ribbing_diagonal: rng.gen(), - christmas_decorations: calendar - .map(|c| c.is_event(CalendarEvent::Christmas)) - .unwrap_or_default(), - }; - - (this, skel) - } - - fn draw( - &self, - index: IndexRef, - _pos: Vec3, - dist: i32, - bound_offset: Vec2, - center_offset: Vec2, - z: i32, - ori: Ori, - locus: i32, - _len: i32, - attr: &Self::Attr, - ) -> BlockMask { - let colors = &index.colors.site.settlement.building.archetype.house; - let roof_color = *self.colors.roof.elim_case_pure(&colors.roof); - let wall_color = *self.colors.wall.elim_case_pure(&colors.wall); - let support_color = *self.colors.support.elim_case_pure(&colors.support); - let christmas_theme = self.christmas_decorations; - - let profile = Vec2::new(bound_offset.x, z); - - let make_block = |(r, g, b)| { - let nz = self - .noise - .get(Vec3::new(center_offset.x, center_offset.y, z * 8)); - BlockMask::new( - Block::new( - BlockKind::Misc, - // TODO: Clarify exactly how this affects the color. - Rgb::new(r, g, b) - .map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)), - ), - 2, - ) - }; - - let facade_layer = 3; - let structural_layer = facade_layer + 1; - let internal_layer = structural_layer + 1; - let foundation_layer = internal_layer + 1; - let floor_layer = foundation_layer + 1; - - let foundation = make_block(colors.foundation).with_priority(foundation_layer); - let log = make_block(support_color); - let floor = make_block(colors.floor); - let wall = make_block(wall_color).with_priority(facade_layer); - let roof = make_block(roof_color).with_priority(facade_layer - 1); - const EMPTY: BlockMask = BlockMask::nothing(); - // TODO: Take environment into account. - let internal = BlockMask::new(Block::air(SpriteKind::Empty), internal_layer); - let end_ori = match ori { - Ori::East => 2, - Ori::North => 4, - }; - let end_window = BlockMask::new( - Block::air(attr.window).with_ori(end_ori).unwrap(), - structural_layer, - ); - let fire = BlockMask::new(Block::air(SpriteKind::Ember), foundation_layer); - - let storey_height = 6; - let storey = ((z - 1) / storey_height).min(attr.levels - 1); - let floor_height = storey_height * storey; - let ceil_height = storey_height * (storey + 1); - let lower_width = locus - 1; - let upper_width = locus; - let width = if profile.y >= ceil_height { - upper_width - } else { - lower_width - }; - let foundation_height = 0 - (dist - width - 1).max(0); - let roof_top = storey_height * attr.levels + 2 + width; - - let edge_ori = if bound_offset.x.abs() > bound_offset.y.abs() { - if center_offset.x > 0 { 6 } else { 2 } - } else if (center_offset.y > 0) ^ (ori == Ori::East) { - 0 - } else { - 4 - }; - let edge_ori = if ori == Ori::East { - (edge_ori + 2) % 8 - } else { - edge_ori - }; - - if let Pillar::Chimney(chimney_height) = attr.pillar { - let chimney_top = roof_top + chimney_height; - // Chimney shaft - if center_offset.map(|e| e.abs()).reduce_max() == 0 && profile.y > foundation_height { - return if profile.y == foundation_height + 1 { - fire - } else { - internal.with_priority(foundation_layer) - }; - } - - // Chimney - if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < chimney_top { - // Fireplace - if center_offset.product() == 0 - && profile.y > foundation_height + 1 - && profile.y <= foundation_height + 3 - { - return internal; - } else { - return foundation; - } - } - } - - if profile.y <= foundation_height && dist < width + 3 { - // Foundations - if attr.storey_fill.has_lower() { - if dist == width - 1 { - // Floor lining - return log.with_priority(floor_layer); - } else if dist < width - 1 && profile.y == foundation_height { - // Floor - return floor.with_priority(floor_layer); - } - } - - if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { - // Basement - return internal; - } else { - return foundation.with_priority(1); - } - } - - // Roofs and walls - let do_roof_wall = - |profile: Vec2, width, dist, bound_offset: Vec2, roof_top, mansard| { - // Roof - - let (roof_profile, roof_dist) = match &attr.roof_style { - RoofStyle::Hip => (Vec2::new(dist, profile.y), dist), - RoofStyle::Gable => (profile, dist), - RoofStyle::Rounded => { - let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25) - - 0.5) - .ceil() as i32; - (Vec2::new(circular_dist, profile.y), circular_dist) - }, - }; - - let roof_level = roof_top - roof_profile.x.max(mansard); - - if profile.y > roof_level { - return EMPTY; - } - - // Roof - if profile.y == roof_level && roof_dist <= width + 2 { - let is_ribbing = ((profile.y - ceil_height) % 3 == 0 && self.roof_ribbing) - || (bound_offset.x == bound_offset.y && self.roof_ribbing_diagonal); - if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing - { - // Eaves - return log; - } else { - return roof; - } - } - - // Wall - - if dist == width && profile.y < roof_level { - // Doors - if center_offset.x > 0 - && center_offset.y > 0 - && bound_offset.x > 0 - && bound_offset.x < width - && profile.y < ceil_height - && attr.storey_fill.has_lower() - && storey == 0 - { - return if (bound_offset.x == (width - 1) / 2 - || bound_offset.x == (width - 1) / 2 + 1) - && profile.y <= foundation_height + 3 - { - // Doors on first floor only - if profile.y == foundation_height + 1 { - BlockMask::new( - Block::air(SpriteKind::Door) - .with_ori( - match ori { - Ori::East => 2, - Ori::North => 0, - } + if bound_offset.x == (width - 1) / 2 { - 0 - } else { - 4 - }, - ) - .unwrap(), - structural_layer, - ) - } else { - EMPTY.with_priority(structural_layer) - } - } else { - wall - }; - } - - if bound_offset.x == bound_offset.y || profile.y == ceil_height { - // Support beams - return log; - } else if !attr.storey_fill.has_lower() && profile.y < ceil_height - || !attr.storey_fill.has_upper() - { - return EMPTY; - } else { - let (frame_bounds, frame_borders) = if profile.y >= ceil_height { - ( - Aabr { - min: Vec2::new(-1, ceil_height + 2), - max: Vec2::new(1, ceil_height + 5), - }, - Vec2::new(1, 1), - ) - } else { - ( - Aabr { - min: Vec2::new(2, floor_height + 2), - max: Vec2::new(width - 2, ceil_height - 2), - }, - Vec2::new(1, 0), - ) - }; - let window_bounds = Aabr { - min: (frame_bounds.min + frame_borders) - .map2(frame_bounds.center(), |a, b| a.min(b)), - max: (frame_bounds.max - frame_borders) - .map2(frame_bounds.center(), |a, b| a.max(b)), - }; - - // Window - if (frame_bounds.size() + 1).reduce_min() > 2 { - // Window frame is large enough for a window - let surface_pos = Vec2::new(bound_offset.x, profile.y); - if window_bounds.contains_point(surface_pos) { - return end_window; - } else if frame_bounds.contains_point(surface_pos) { - return log.with_priority(structural_layer); - }; - } - - // Wall - return if attr.central_supports && profile.x == 0 { - // Support beams - log.with_priority(structural_layer) - } else { - wall - }; - } - } - - if dist < width { - // Internals - if profile.y == ceil_height { - if profile.x == 0 { - // Rafters - return log; - } else if attr.storey_fill.has_upper() { - // Ceiling - return floor; - } - } else if !attr.storey_fill.has_lower() - && center_offset.sum() % 2 == 0 - && profile.y == 1 - && center_offset.map(|e| e % 3 == 0).reduce_and() - && self - .noise - .chance(Vec3::new(center_offset.x, center_offset.y, z), 0.8) - { - let furniture = match self.noise.get(Vec3::new( - center_offset.x, - center_offset.y, - z + 100, - )) % 13 - { - 0..=1 => SpriteKind::Crate, - 2 => SpriteKind::Bench, - 3 => SpriteKind::Anvil, - 4 => SpriteKind::CookingPot, - 5 => SpriteKind::CraftingBench, - 6 => SpriteKind::FireBowlGround, - 7 => SpriteKind::Cauldron, - 8 => SpriteKind::Forge, - 9 => SpriteKind::Loom, - 10 => SpriteKind::SpinningWheel, - 11 => SpriteKind::TanningRack, - 12 => SpriteKind::DismantlingBench, - _ => unreachable!(), - }; - - return BlockMask::new(Block::air(furniture).with_ori(end_ori).unwrap(), 1); - } else if (!attr.storey_fill.has_lower() && profile.y < ceil_height) - || (!attr.storey_fill.has_upper() && profile.y >= ceil_height) - { - return EMPTY; - // Furniture - } else if dist == width - 1 - && center_offset.sum() % 2 == 0 - && profile.y == floor_height + 1 - && self - .noise - .chance(Vec3::new(center_offset.x, center_offset.y, z), 0.2) - { - // NOTE: Used only for dynamic elements like chests and entities! - let mut dynamic_rng = rand::thread_rng(); - let furniture = match self.noise.get(Vec3::new( - center_offset.x, - center_offset.y, - z + 100, - )) % 12 - { - 0 => SpriteKind::Planter, - 1 => SpriteKind::ChairSingle, - 2 => SpriteKind::ChairDouble, - 3 => SpriteKind::CoatRack, - 4 => { - if dynamic_rng.gen_range(0..8) == 0 { - SpriteKind::Chest - } else { - SpriteKind::Crate - } - }, - 6 => SpriteKind::DrawerMedium, - 7 => SpriteKind::DrawerSmall, - 8 => SpriteKind::TableSide, - 9 => SpriteKind::WardrobeSingle, - 10 => { - if dynamic_rng.gen_range(0..10) == 0 { - SpriteKind::PotionMinor - } else { - SpriteKind::VialEmpty - } - }, - _ => { - if dynamic_rng.gen_range(0..2) == 0 { - SpriteKind::Bowl - } else { - SpriteKind::Pot - } - }, - }; - - return BlockMask::new( - Block::air(furniture).with_ori(edge_ori).unwrap(), - internal_layer, - ); - } else { - return internal; - } - } - - // Wall ornaments - if dist == width + 1 - && center_offset.map(|e| e.abs()).reduce_min() == 0 - && profile.y == floor_height + 3 - && self.noise.chance( - Vec3::new(center_offset.x, center_offset.y, z), - if christmas_theme { 0.70 } else { 0.35 }, - ) - && attr.storey_fill.has_lower() - { - let ornament = if christmas_theme { - match self - .noise - .get(Vec3::new(center_offset.x, center_offset.y, z + 100)) - % 4 - { - 0 => SpriteKind::ChristmasWreath, - _ => SpriteKind::ChristmasOrnament, - } - } else { - match self - .noise - .get(Vec3::new(center_offset.x, center_offset.y, z + 100)) - % 6 - { - 0 => SpriteKind::HangingSign, - 1 | 2 | 3 => SpriteKind::HangingBasket, - 4 => SpriteKind::WallSconce, - 5 => SpriteKind::WallLampSmall, - _ => SpriteKind::DungeonWallDecor, - } - }; - - BlockMask::new( - Block::air(ornament).with_ori((edge_ori + 4) % 8).unwrap(), - internal_layer, - ) - } else { - EMPTY - } - }; - - let mut cblock = do_roof_wall(profile, width, dist, bound_offset, roof_top, attr.mansard); - - if let Pillar::Tower(tower_height) = attr.pillar { - let tower_top = roof_top + tower_height; - let profile = Vec2::new(center_offset.x.abs(), profile.y); - let dist = center_offset.map(|e| e.abs()).reduce_max(); - - cblock = cblock.resolve_with(do_roof_wall( - profile, - 4, - dist, - center_offset.map(|e| e.abs()), - tower_top, - attr.mansard, - )); - } - - cblock - } -} diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs deleted file mode 100644 index 065d34dd53..0000000000 --- a/world/src/site/settlement/building/archetype/keep.rs +++ /dev/null @@ -1,287 +0,0 @@ -use super::{super::skeleton::*, Archetype}; -use crate::{ - site::BlockMask, - util::{RandomField, Sampler}, - IndexRef, -}; -use common::{ - calendar::Calendar, - make_case_elim, - terrain::{Block, BlockKind, SpriteKind}, -}; -use rand::prelude::*; -use serde::Deserialize; -use vek::*; - -#[derive(Deserialize)] -pub struct Colors { - pub brick_base: (u8, u8, u8), - pub floor_base: (u8, u8, u8), - pub pole: (u8, u8, u8), - pub flag: flag_color::PureCases<(u8, u8, u8)>, - pub stone: stone_color::PureCases<(u8, u8, u8)>, -} - -pub struct Keep { - pub flag_color: FlagColor, - pub stone_color: StoneColor, -} - -pub struct Attr { - pub storeys: i32, - pub is_tower: bool, - pub flag: bool, - pub ridged: bool, - pub rounded: bool, - pub has_doors: bool, -} - -make_case_elim!( - flag_color, - #[repr(u32)] - pub enum FlagColor { - Good = 0, - Evil = 1, - } -); - -make_case_elim!( - stone_color, - #[repr(u32)] - pub enum StoneColor { - Good = 0, - Evil = 1, - } -); - -impl Archetype for Keep { - type Attr = Attr; - - fn generate(rng: &mut R, _calendar: Option<&Calendar>) -> (Self, Skeleton) { - let len = rng.gen_range(-8..24).max(0); - let storeys = rng.gen_range(1..3); - let skel = Skeleton { - offset: -rng.gen_range(0..len + 7).clamped(0, len), - ori: if rng.gen() { Ori::East } else { Ori::North }, - root: Branch { - len, - attr: Attr { - storeys, - is_tower: false, - flag: false, - ridged: false, - rounded: true, - has_doors: true, - }, - locus: 10 + rng.gen_range(0..5), - border: 3, - children: (0..1) - .map(|_| { - ( - rng.gen_range(-5..len + 5).clamped(0, len.max(1) - 1), - Branch { - len: 0, - attr: Attr { - storeys: storeys + rng.gen_range(1..3), - is_tower: true, - flag: true, - ridged: false, - rounded: true, - has_doors: false, - }, - locus: 6 + rng.gen_range(0..3), - border: 3, - children: Vec::new(), - }, - ) - }) - .collect(), - }, - }; - - ( - Self { - flag_color: FlagColor::Good, - stone_color: StoneColor::Good, - }, - skel, - ) - } - - fn draw( - &self, - index: IndexRef, - pos: Vec3, - _dist: i32, - bound_offset: Vec2, - center_offset: Vec2, - z: i32, - ori: Ori, - locus: i32, - _len: i32, - attr: &Self::Attr, - ) -> BlockMask { - let dungeon_stone = index.colors.site.dungeon.stone; - let colors = &index.colors.site.settlement.building.archetype.keep; - let flag_color = self.flag_color.elim_case_pure(&colors.flag); - let stone_color = self.stone_color.elim_case_pure(&colors.stone); - - let profile = Vec2::new(bound_offset.x, z); - - let weak_layer = 1; - let normal_layer = weak_layer + 1; - let important_layer = normal_layer + 1; - let internal_layer = important_layer + 1; - - let make_block = - |r, g, b| BlockMask::new(Block::new(BlockKind::Rock, Rgb::new(r, g, b)), normal_layer); - - let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1); - let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24; - let foundation = make_block( - colors.brick_base.0 + brick_tex, - colors.brick_base.1 + brick_tex, - colors.brick_base.2 + brick_tex, - ); - let wall = make_block( - stone_color.0 + brick_tex, - stone_color.1 + brick_tex, - stone_color.2 + brick_tex, - ); - let window = BlockMask::new( - Block::air(SpriteKind::Window1) - .with_ori(match ori { - Ori::East => 2, - Ori::North => 0, - }) - .unwrap(), - normal_layer, - ); - let floor = make_block( - colors.floor_base.0 + (pos.y.abs() % 2) as u8 * 15, - colors.floor_base.1 + (pos.y.abs() % 2) as u8 * 15, - colors.floor_base.2 + (pos.y.abs() % 2) as u8 * 15, - ) - .with_priority(important_layer); - let pole = - make_block(colors.pole.0, colors.pole.1, colors.pole.2).with_priority(important_layer); - let flag = - make_block(flag_color.0, flag_color.1, flag_color.2).with_priority(important_layer); - const AIR: Block = Block::air(SpriteKind::Empty); - const EMPTY: BlockMask = BlockMask::nothing(); - let internal = BlockMask::new(AIR, internal_layer); - - let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { - let stone = BlockMask::new(Block::new(BlockKind::Rock, dungeon_stone.into()), 5); - - if (pos.xy().magnitude_squared() as f32) < inner_radius.powi(2) { - stone - } else if (pos.xy().magnitude_squared() as f32) < radius.powi(2) { - if ((pos.x as f32).atan2(pos.y as f32) / (std::f32::consts::PI * 2.0) * stretch - + pos.z as f32) - .rem_euclid(stretch) - < 1.5 - { - stone - } else { - internal - } - } else { - EMPTY - } - }; - - let ridge_x = (center_offset.map(|e| e.abs()).reduce_min() + 2) % 8; - let width = locus - + if ridge_x < 4 && attr.ridged && !attr.rounded { - 1 - } else { - 0 - }; - let rampart_width = 2 + width; - let storey_height = 9; - let roof_height = attr.storeys * storey_height; - let storey_y = profile.y % storey_height; - let door_height = 6; - let rampart_height = roof_height + if ridge_x % 2 == 0 { 3 } else { 4 }; - let min_dist = if attr.rounded { - bound_offset.map(|e| e.pow(2) as f32).sum().sqrt() as i32 - } else { - bound_offset.map(|e| e.abs()).reduce_max() - }; - - if profile.y <= 0 - (min_dist - width - 1).max(0) && min_dist < width + 3 { - // Foundations - foundation - } else if (0..=roof_height).contains(&profile.y) && storey_y == 0 && min_dist <= width + 1 { - if min_dist < width { floor } else { wall } - } else if bound_offset.x.abs() < 3 - && profile.y < door_height - bound_offset.x.abs() - && profile.y > 0 - && min_dist >= width - 2 - && min_dist <= width + 1 - && attr.has_doors - { - internal - } else if (min_dist == width || (!attr.is_tower && min_dist == width + 1)) - && profile.y <= roof_height - { - if attr.is_tower - && (3..7).contains(&storey_y) - && bound_offset.x.abs() < width - 2 - && (5..7).contains(&ridge_x) - { - window - } else { - wall - } - } else if profile.y >= roof_height { - if profile.y > roof_height - && (min_dist < rampart_width - 1 || (attr.is_tower && min_dist < rampart_width)) - { - if attr.is_tower - && attr.flag - && center_offset == Vec2::zero() - && profile.y < roof_height + 16 - { - pole - } else if attr.is_tower - && attr.flag - && center_offset.x == 0 - && center_offset.y > 0 - && center_offset.y < 8 - && profile.y > roof_height + 8 - && profile.y < roof_height + 14 - { - flag - } else { - EMPTY - } - } else if min_dist <= rampart_width { - if profile.y < rampart_height { - wall - } else { - EMPTY - } - } else { - EMPTY - } - } else if profile.y < roof_height && min_dist < width { - internal - } else { - EMPTY - } - .resolve_with( - if attr.is_tower && profile.y > 0 && profile.y <= roof_height { - make_staircase( - Vec3::new(center_offset.x, center_offset.y, pos.z), - 7.0f32.min(width as f32 - 1.0), - 0.5, - 9.0, - ) - } else { - EMPTY - }, - ) - } -} diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs deleted file mode 100644 index e09a59205f..0000000000 --- a/world/src/site/settlement/building/archetype/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -pub mod house; -pub mod keep; - -use super::skeleton::*; -use crate::{site::BlockMask, IndexRef}; -use common::calendar::Calendar; -use rand::prelude::*; -use serde::Deserialize; -use vek::*; - -#[derive(Deserialize)] -pub struct Colors { - pub house: house::Colors, - pub keep: keep::Colors, -} - -pub trait Archetype { - type Attr; - - fn generate(rng: &mut R, calendar: Option<&Calendar>) -> (Self, Skeleton) - where - Self: Sized; - - fn draw( - &self, - index: IndexRef, - pos: Vec3, - dist: i32, - bound_offset: Vec2, - center_offset: Vec2, - z: i32, - ori: Ori, - locus: i32, - len: i32, - attr: &Self::Attr, - ) -> BlockMask; -} diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs deleted file mode 100644 index 4f0a290957..0000000000 --- a/world/src/site/settlement/building/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -pub mod archetype; -pub mod skeleton; - -// Reexports -pub use self::{ - archetype::{house::House, keep::Keep, Archetype}, - skeleton::*, -}; - -use crate::IndexRef; -use common::{calendar::Calendar, terrain::Block}; -use rand::prelude::*; -use serde::Deserialize; -use vek::*; - -#[derive(Deserialize)] -pub struct Colors { - pub archetype: archetype::Colors, -} - -pub struct Building { - skel: Skeleton, - archetype: A, - origin: Vec3, -} - -impl Building { - pub fn generate(rng: &mut impl Rng, origin: Vec3, calendar: Option<&Calendar>) -> Self - where - A: Sized, - { - let (archetype, skel) = A::generate(rng, calendar); - Self { - skel, - archetype, - origin, - } - } - - pub fn bounds_2d(&self) -> Aabr { - let b = self.skel.bounds(); - Aabr { - min: Vec2::from(self.origin) + b.min, - max: Vec2::from(self.origin) + b.max, - } - } - - pub fn bounds(&self) -> Aabb { - let aabr = self.bounds_2d(); - Aabb { - min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8), - max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 48), - } - } - - pub fn sample(&self, index: IndexRef, pos: Vec3) -> Option { - let rpos = pos - self.origin; - self.skel - .sample_closest( - rpos, - |pos, dist, bound_offset, center_offset, ori, branch| { - self.archetype.draw( - index, - pos, - dist, - bound_offset, - center_offset, - rpos.z, - ori, - branch.locus, - branch.len, - &branch.attr, - ) - }, - ) - .finish() - } -} diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs deleted file mode 100644 index 2b01c8feb1..0000000000 --- a/world/src/site/settlement/building/skeleton.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::site::BlockMask; -use vek::*; - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum Ori { - East, - North, -} - -impl Ori { - #[must_use] - pub fn flip(self) -> Self { - match self { - Ori::East => Ori::North, - Ori::North => Ori::East, - } - } - - pub fn dir(self) -> Vec2 { - match self { - Ori::East => Vec2::unit_x(), - Ori::North => Vec2::unit_y(), - } - } -} - -pub struct Branch { - pub len: i32, - pub attr: T, - pub locus: i32, - pub border: i32, - pub children: Vec<(i32, Branch)>, -} - -impl Branch { - fn for_each<'a>( - &'a self, - node: Vec2, - ori: Ori, - is_child: bool, - parent_locus: i32, - f: &mut impl FnMut(Vec2, Ori, &'a Branch, bool, i32), - ) { - f(node, ori, self, is_child, parent_locus); - for (offset, child) in &self.children { - child.for_each(node + ori.dir() * *offset, ori.flip(), true, self.locus, f); - } - } -} - -pub struct Skeleton { - pub offset: i32, - pub ori: Ori, - pub root: Branch, -} - -impl Skeleton { - pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch, bool, i32)) { - self.root - .for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f); - } - - pub fn bounds(&self) -> Aabr { - let mut bounds = Aabr::new_empty(self.ori.dir() * self.offset); - self.for_each(|node, ori, branch, _, _| { - let node2 = node + ori.dir() * branch.len; - - let a = node.map2(node2, |a, b| a.min(b)) - (branch.locus + branch.border); - let b = node.map2(node2, |a, b| a.max(b)) + (branch.locus + branch.border); - bounds.expand_to_contain_point(a); - bounds.expand_to_contain_point(b); - }); - bounds - } - - pub fn sample_closest( - &self, - pos: Vec3, - mut f: impl FnMut(Vec3, i32, Vec2, Vec2, Ori, &Branch) -> BlockMask, - ) -> BlockMask { - let mut min = None::<(_, BlockMask)>; - self.for_each(|node, ori, branch, is_child, parent_locus| { - let node2 = node + ori.dir() * branch.len; - let node = node - + if is_child { - ori.dir() - * branch.len.signum() - * (branch.locus - parent_locus).clamped(0, branch.len.abs()) - } else { - Vec2::zero() - }; - let bounds = Aabr::new_empty(node).expanded_to_contain_point(node2); - let bound_offset = if ori == Ori::East { - Vec2::new( - node.y - pos.y, - pos.x - pos.x.clamped(bounds.min.x, bounds.max.x), - ) - } else { - Vec2::new( - node.x - pos.x, - pos.y - pos.y.clamped(bounds.min.y, bounds.max.y), - ) - } - .map(|e| e.abs()); - let center_offset = if ori == Ori::East { - Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x) - } else { - Vec2::new(pos.x - bounds.center().x, pos.y - bounds.center().y) - }; - let dist = bound_offset.reduce_max(); - let dist_locus = dist - branch.locus; - let new_bm = f(pos, dist, bound_offset, center_offset, ori, branch); - min = min - .map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm))) - .or(Some((dist_locus, new_bm))); - }); - min.map(|(_, bm)| bm).unwrap_or_else(BlockMask::nothing) - } -} diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs deleted file mode 100644 index 6a2f9dd573..0000000000 --- a/world/src/site/settlement/mod.rs +++ /dev/null @@ -1,1443 +0,0 @@ -pub mod building; -mod town; - -use self::{ - building::{Building, House, Keep}, - town::{District, Town}, -}; -use super::SpawnRules; -use crate::{ - column::ColumnSample, - sim::WorldSim, - site::namegen::NameGen, - util::{RandomField, Sampler, StructureGen2d}, - IndexRef, -}; -use common::{ - astar::Astar, - comp::{ - self, agent, bird_medium, - inventory::{ - loadout_builder::LoadoutBuilder, slot::ArmorSlot, trade_pricing::TradePricing, - }, - quadruped_small, Item, - }, - generation::{ChunkSupplement, EntityInfo}, - path::Path, - spiral::Spiral2d, - store::{Id, Store}, - terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, - trade::{self, Good, SiteInformation}, - vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol}, -}; - -use fxhash::FxHasher64; -use hashbrown::{HashMap, HashSet}; -use rand::prelude::*; -use serde::Deserialize; -use std::{collections::VecDeque, f32, hash::BuildHasherDefault}; -use vek::*; - -#[derive(Deserialize)] -pub struct Colors { - pub building: building::Colors, - - pub plot_town_path: (u8, u8, u8), - - pub plot_field_dirt: (u8, u8, u8), - pub plot_field_mound: (u8, u8, u8), - - pub wall_low: (u8, u8, u8), - pub wall_high: (u8, u8, u8), - - pub tower_color: (u8, u8, u8), - - pub plot_dirt: (u8, u8, u8), - pub plot_grass: (u8, u8, u8), - pub plot_water: (u8, u8, u8), - pub plot_town: (u8, u8, u8), -} - -#[allow(dead_code)] -pub fn gradient(line: [Vec2; 2]) -> f32 { - let r = (line[0].y - line[1].y) / (line[0].x - line[1].x); - if r.is_nan() { 100000.0 } else { r } -} - -#[allow(dead_code)] -pub fn intersect(a: [Vec2; 2], b: [Vec2; 2]) -> Option> { - let ma = gradient(a); - let mb = gradient(b); - - let ca = a[0].y - ma * a[0].x; - let cb = b[0].y - mb * b[0].x; - - if (ma - mb).abs() < 0.0001 || (ca - cb).abs() < 0.0001 { - None - } else { - let x = (cb - ca) / (ma - mb); - let y = ma * x + ca; - - Some(Vec2::new(x, y)) - } -} - -#[allow(dead_code)] -pub fn center_of(p: [Vec2; 3]) -> Vec2 { - let ma = -1.0 / gradient([p[0], p[1]]); - let mb = -1.0 / gradient([p[1], p[2]]); - - let pa = (p[0] + p[1]) * 0.5; - let pb = (p[1] + p[2]) * 0.5; - - let ca = pa.y - ma * pa.x; - let cb = pb.y - mb * pb.x; - - let x = (cb - ca) / (ma - mb); - let y = ma * x + ca; - - Vec2::new(x, y) -} - -impl WorldSim { - fn can_host_settlement(&self, pos: Vec2) -> bool { - self.get(pos) - .map(|chunk| !chunk.river.is_river() && !chunk.river.is_lake()) - .unwrap_or(false) - && self - .get_gradient_approx(pos) - .map(|grad| grad < 0.75) - .unwrap_or(false) - } -} - -const AREA_SIZE: u32 = 32; - -fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } - -pub enum StructureKind { - House(Building), - Keep(Building), -} - -pub struct Structure { - kind: StructureKind, -} - -impl Structure { - pub fn bounds_2d(&self) -> Aabr { - match &self.kind { - StructureKind::House(house) => house.bounds_2d(), - StructureKind::Keep(keep) => keep.bounds_2d(), - } - } - - pub fn bounds(&self) -> Aabb { - match &self.kind { - StructureKind::House(house) => house.bounds(), - StructureKind::Keep(keep) => keep.bounds(), - } - } - - pub fn sample(&self, index: IndexRef, rpos: Vec3) -> Option { - match &self.kind { - StructureKind::House(house) => house.sample(index, rpos), - StructureKind::Keep(keep) => keep.sample(index, rpos), - } - } -} - -pub struct Settlement { - name: String, - seed: u32, - origin: Vec2, - land: Land, - farms: Store, - structures: Vec, - town: Option, - noise: RandomField, -} - -pub struct Farm { - #[allow(dead_code)] - base_tile: Vec2, -} - -pub struct GenCtx<'a, R: Rng> { - sim: Option<&'a WorldSim>, - rng: &'a mut R, -} - -impl Settlement { - pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { - let mut ctx = GenCtx { sim, rng }; - let mut this = Self { - name: NameGen::location(ctx.rng).generate(), - seed: ctx.rng.gen(), - origin: wpos, - land: Land::new(ctx.rng), - farms: Store::default(), - structures: Vec::new(), - town: None, - noise: RandomField::new(ctx.rng.gen()), - }; - - if let Some(sim) = ctx.sim { - this.designate_from_world(sim, ctx.rng); - } - - //this.place_river(rng); - - this.place_farms(&mut ctx); - this.place_town(&mut ctx); - //this.place_paths(ctx.rng); - this.place_buildings(&mut ctx); - - this - } - - pub fn name(&self) -> &str { &self.name } - - pub fn get_origin(&self) -> Vec2 { self.origin } - - /// Designate hazardous terrain based on world data - pub fn designate_from_world(&mut self, sim: &WorldSim, rng: &mut impl Rng) { - let tile_radius = self.radius() as i32 / AREA_SIZE as i32; - let hazard = self.land.hazard; - Spiral2d::new() - .take_while(|tile| tile.map(|e| e.abs()).reduce_max() < tile_radius) - .for_each(|tile| { - let wpos = self.origin + tile * AREA_SIZE as i32; - - if (0..4) - .flat_map(|x| (0..4).map(move |y| Vec2::new(x, y))) - .any(|offs| { - let wpos = wpos + offs * AREA_SIZE as i32 / 2; - let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32)); - !sim.can_host_settlement(cpos) - }) - || rng.gen_range(0..16) == 0 - // Randomly consider some tiles inaccessible - { - self.land.set(tile, hazard); - } - }) - } - - /// Testing only - pub fn place_river(&mut self, rng: &mut impl Rng) { - let river_dir = Vec2::new(rng.gen::() - 0.5, rng.gen::() - 0.5).normalized(); - let radius = 500.0 + rng.gen::().powi(2) * 1000.0; - let river = self.land.new_plot(Plot::Water); - let river_offs = Vec2::new(rng.gen_range(-3..4), rng.gen_range(-3..4)); - - for x in (0..100).map(|e| e as f32 / 100.0) { - let theta0 = x as f32 * f32::consts::PI * 2.0; - let theta1 = (x + 0.01) as f32 * f32::consts::PI * 2.0; - - let pos0 = (river_dir * radius + Vec2::new(theta0.sin(), theta0.cos()) * radius) - .map(|e| e.floor() as i32) - .map(to_tile) - + river_offs; - let pos1 = (river_dir * radius + Vec2::new(theta1.sin(), theta1.cos()) * radius) - .map(|e| e.floor() as i32) - .map(to_tile) - + river_offs; - - if pos0.magnitude_squared() > 15i32.pow(2) { - continue; - } - - if let Some(path) = self.land.find_path(pos0, pos1, |_, _| 1.0) { - for pos in path.iter().copied() { - self.land.set(pos, river); - } - } - } - } - - pub fn place_paths(&mut self, rng: &mut impl Rng) { - const PATH_COUNT: usize = 6; - - let mut dir = Vec2::zero(); - for _ in 0..PATH_COUNT { - dir = (Vec2::new(rng.gen::() - 0.5, rng.gen::() - 0.5) * 2.0 - dir) - .try_normalized() - .unwrap_or_else(Vec2::zero); - let origin = dir.map(|e| (e * 100.0) as i32); - let origin = self - .land - .find_tile_near(origin, |plot| matches!(plot, Some(&Plot::Field { .. }))) - .unwrap(); - - if let Some(path) = self.town.as_ref().and_then(|town| { - self.land - .find_path(origin, town.base_tile, |from, to| match (from, to) { - (_, Some(b)) if self.land.plot(b.plot) == &Plot::Dirt => 0.0, - (_, Some(b)) if self.land.plot(b.plot) == &Plot::Water => 20.0, - (_, Some(b)) if self.land.plot(b.plot) == &Plot::Hazard => 50.0, - (Some(a), Some(b)) if a.contains(WayKind::Wall) => { - if b.contains(WayKind::Wall) { - 1000.0 - } else { - 10.0 - } - }, - (Some(_), Some(_)) => 1.0, - _ => 1000.0, - }) - }) { - let path = path.iter().copied().collect::>(); - self.land.write_path(&path, WayKind::Path, |_| true, false); - } - } - } - - pub fn place_town(&mut self, ctx: &mut GenCtx) { - const PLOT_COUNT: usize = 3; - - let mut origin = Vec2::new(ctx.rng.gen_range(-2..3), ctx.rng.gen_range(-2..3)); - - for i in 0..PLOT_COUNT { - if let Some(base_tile) = self.land.find_tile_near(origin, |plot| { - matches!(plot, Some(Plot::Field { .. }) | Some(Plot::Dirt)) - }) { - // self.land - // .plot_at_mut(base_tile) - // .map(|plot| *plot = Plot::Town { district: None }); - - if i == 0 { - let town = Town::generate(self.origin, base_tile, ctx); - - for (id, district) in town.districts().iter() { - let district_plot = - self.land.plots.insert(Plot::Town { district: Some(id) }); - - for x in district.aabr.min.x..district.aabr.max.x { - for y in district.aabr.min.y..district.aabr.max.y { - if !matches!(self.land.plot_at(Vec2::new(x, y)), Some(Plot::Hazard)) - { - self.land.set(Vec2::new(x, y), district_plot); - } - } - } - } - - self.town = Some(town); - origin = base_tile; - } - } - } - - // Boundary wall - /* - let spokes = CARDINALS - .iter() - .filter_map(|dir| { - self.land.find_tile_dir(origin, *dir, |plot| match plot { - Some(Plot::Water) => false, - Some(Plot::Town) => false, - _ => true, - }) - }) - .collect::>(); - let mut wall_path = Vec::new(); - for i in 0..spokes.len() { - self.land - .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to - .map(|to| self.land.plot(to.plot)) - { - Some(Plot::Hazard) => 200.0, - Some(Plot::Water) => 40.0, - Some(Plot::Town) => 10000.0, - _ => 10.0, - }) - .map(|path| wall_path.extend(path.iter().copied())); - } - let grass = self.land.new_plot(Plot::Grass); - let buildable = |plot: &Plot| match plot { - Plot::Water => false, - _ => true, - }; - for pos in wall_path.iter() { - if self.land.tile_at(*pos).is_none() { - self.land.set(*pos, grass); - } - if self.land.plot_at(*pos).copied().filter(buildable).is_some() { - self.land - .tile_at_mut(*pos) - .map(|tile| tile.tower = Some(Tower::Wall)); - } - } - if wall_path.len() > 0 { - wall_path.push(wall_path[0]); - } - self.land - .write_path(&wall_path, WayKind::Wall, buildable, true); - */ - } - - pub fn place_buildings(&mut self, ctx: &mut GenCtx) { - let town_center = if let Some(town) = self.town.as_ref() { - town.base_tile - } else { - return; - }; - - for tile in Spiral2d::new() - .map(|offs| town_center + offs) - .take(16usize.pow(2)) - { - // This is a stupid way to decide how to place buildings - for i in 0..ctx.rng.gen_range(2..5) { - for _ in 0..25 { - let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) - + Vec2::::zero().map(|_| { - ctx.rng - .gen_range(-(AREA_SIZE as i32) / 4..AREA_SIZE as i32 / 4) - }); - - let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32)); - if self - .land - .tile_at(tile_pos) - .map(|t| t.contains(WayKind::Path)) - .unwrap_or(true) - || ctx - .sim - .and_then(|sim| sim.get_nearest_path(self.origin + house_pos)) - .map(|(dist, _, _, _)| dist < 28.0) - .unwrap_or(false) - { - continue; - } - - let alt = if let Some(Plot::Town { district }) = self.land.plot_at(tile_pos) { - district - .and_then(|d| self.town.as_ref().map(|t| t.districts().get(d))) - .map(|d| d.alt) - .filter(|_| false) // Temporary - .unwrap_or_else(|| { - ctx.sim - .and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) - .unwrap_or(0.0) - .ceil() as i32 - }) - } else { - continue; - }; - - let structure = Structure { - kind: if tile == town_center && i == 0 { - StructureKind::Keep(Building::::generate( - ctx.rng, - Vec3::new(house_pos.x, house_pos.y, alt), - None, - )) - } else { - StructureKind::House(Building::::generate( - ctx.rng, - Vec3::new(house_pos.x, house_pos.y, alt), - ctx.sim.and_then(|sim| sim.calendar.as_ref()), - )) - }, - }; - - let bounds = structure.bounds_2d(); - - // Check for collision with other structures - if self - .structures - .iter() - .any(|s| s.bounds_2d().collides_with_aabr(bounds)) - { - continue; - } - - self.structures.push(structure); - break; - } - } - } - } - - pub fn place_farms(&mut self, ctx: &mut GenCtx) { - const FARM_COUNT: usize = 6; - const FIELDS_PER_FARM: usize = 5; - - for _ in 0..FARM_COUNT { - if let Some(base_tile) = self - .land - .find_tile_near(Vec2::zero(), |plot| plot.is_none()) - { - // Farm - //let farmhouse = self.land.new_plot(Plot::Dirt); - //self.land.set(base_tile, farmhouse); - - // Farmhouses - // for _ in 0..ctx.rng.gen_range(1..3) { - // let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 - // / 2) + Vec2::new(ctx.rng.gen_range(-16..16), - // ctx.rng.gen_range(-16..16)); - - // self.structures.push(Structure { - // kind: StructureKind::House(HouseBuilding::generate(ctx.rng, - // Vec3::new( house_pos.x, - // house_pos.y, - // ctx.sim - // .and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) - // .unwrap_or(0.0) - // .ceil() as i32, - // ))), - // }); - // } - - // Fields - let farmland = self.farms.insert(Farm { base_tile }); - for _ in 0..FIELDS_PER_FARM { - self.place_field(farmland, base_tile, ctx.rng); - } - } - } - } - - pub fn place_field( - &mut self, - farm: Id, - origin: Vec2, - rng: &mut impl Rng, - ) -> Option> { - const MAX_FIELD_SIZE: usize = 24; - - if let Some(center) = self.land.find_tile_near(origin, |plot| plot.is_none()) { - let field = self.land.new_plot(Plot::Field { - farm, - seed: rng.gen(), - crop: match rng.gen_range(0..8) { - 0 => Crop::Corn, - 1 => Crop::Wheat, - 2 => Crop::Cabbage, - 3 => Crop::Pumpkin, - 4 => Crop::Flax, - 5 => Crop::Carrot, - 6 => Crop::Tomato, - 7 => Crop::Radish, - _ => Crop::Sunflower, - }, - }); - let tiles = - self.land - .grow_from(center, rng.gen_range(5..MAX_FIELD_SIZE), rng, |plot| { - plot.is_none() - }); - for pos in tiles.into_iter() { - self.land.set(pos, field); - } - Some(field) - } else { - None - } - } - - pub fn radius(&self) -> f32 { 400.0 } - - pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { - SpawnRules { - trees: self - .land - .get_at_block(wpos - self.origin) - .plot - .map(|p| matches!(p, Plot::Hazard)) - .unwrap_or(true), - ..SpawnRules::default() - } - } - - pub fn apply_to<'a>( - &'a self, - index: IndexRef, - wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample/*<'a>*/>, - vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), - ) { - let colors = &index.colors.site.settlement; - - for y in 0..vol.size_xy().y as i32 { - for x in 0..vol.size_xy().x as i32 { - let offs = Vec2::new(x, y); - - let wpos2d = wpos2d + offs; - let rpos = wpos2d - self.origin; - - // Sample terrain - let col_sample = if let Some(col_sample) = get_column(offs) { - col_sample - } else { - continue; - }; - let land_surface_z = col_sample.riverless_alt.floor() as i32; - let mut surface_z = land_surface_z; - - // Sample settlement - let sample = self.land.get_at_block(rpos); - - let noisy_color = move |col: Rgb, factor: u32| { - let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); - col.map(|e| { - (e as u32 + nz % (factor * 2)) - .saturating_sub(factor) - .min(255) as u8 - }) - }; - - // District alt - if let Some(Plot::Town { district }) = sample.plot { - if let Some(d) = district - .and_then(|d| self.town.as_ref().map(|t| t.districts().get(d))) - .filter(|_| false) - // Temporary - { - let other = self - .land - .plot_at(sample.second_closest) - .and_then(|p| match p { - Plot::Town { district } => *district, - _ => None, - }) - .and_then(|d| { - self.town.as_ref().map(|t| t.districts().get(d).alt as f32) - }) - .filter(|_| false) - .unwrap_or(surface_z as f32); - surface_z = Lerp::lerp( - (other + d.alt as f32) / 2.0, - d.alt as f32, - (1.25 * sample.edge_dist / (d.alt as f32 - other).abs()).min(1.0), - ) as i32; - } - } - - { - let mut surface_sprite = None; - - let roll = - |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; - - let color = match sample.plot { - Some(Plot::Dirt) => Some(colors.plot_dirt.into()), - Some(Plot::Grass) => Some(colors.plot_grass.into()), - Some(Plot::Water) => Some(colors.plot_water.into()), - //Some(Plot::Town { district }) => None, - Some(Plot::Town { .. }) => { - if let Some((_, path_nearest, _, _)) = col_sample.path { - let path_dir = (path_nearest - wpos2d.map(|e| e as f32)) - .rotated_z(f32::consts::PI / 2.0) - .normalized(); - let is_lamp = if path_dir.x.abs() > path_dir.y.abs() { - wpos2d.x as f32 % 15.0 / path_dir.dot(Vec2::unit_y()).abs() - <= 1.0 - } else { - (wpos2d.y as f32 + 10.0) % 15.0 - / path_dir.dot(Vec2::unit_x()).abs() - <= 1.0 - }; - if (col_sample.path.map(|(dist, _, _, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 0) - || (roll(0, 750) == 0 && col_sample.path.map(|(dist, _, _, _)| dist > 20.0).unwrap_or(true)) - { - surface_sprite = Some(SpriteKind::StreetLamp); - } - } - - Some( - Rgb::from(colors.plot_town_path) - .map2(Rgb::iota(), |e: u8, _i: i32| { - e.saturating_add(0_u8).saturating_sub(8) - }), - ) - }, - Some(Plot::Field { seed, crop, .. }) => { - let furrow_dirs = [ - Vec2::new(1, 0), - Vec2::new(0, 1), - Vec2::new(1, 1), - Vec2::new(-1, 1), - ]; - let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; - let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; - - let dirt = Rgb::::from(colors.plot_field_dirt).map(|e| { - e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) - as u8 - }); - let mound = Rgb::::from(colors.plot_field_mound) - .map(|e| e + roll(0, 8) as u8) - .map(|e| { - e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) - % 32) as u8 - }); - - if in_furrow { - if roll(0, 5) == 0 { - surface_sprite = match crop { - Crop::Corn => Some(SpriteKind::Corn), - Crop::Wheat if roll(1, 2) == 0 => { - Some(SpriteKind::WheatYellow) - }, - Crop::Wheat => Some(SpriteKind::WheatGreen), - Crop::Cabbage if roll(2, 2) == 0 => { - Some(SpriteKind::Cabbage) - }, - Crop::Pumpkin if roll(3, 2) == 0 => { - Some(SpriteKind::Pumpkin) - }, - Crop::Flax if roll(4, 2) == 0 => Some(SpriteKind::Flax), - Crop::Carrot if roll(5, 2) == 0 => Some(SpriteKind::Carrot), - Crop::Tomato if roll(6, 2) == 0 => Some(SpriteKind::Tomato), - Crop::Radish if roll(7, 2) == 0 => Some(SpriteKind::Radish), - Crop::Turnip if roll(8, 2) == 0 => Some(SpriteKind::Turnip), - Crop::Sunflower => Some(SpriteKind::Sunflower), - _ => surface_sprite, - } - .or_else(|| { - if roll(9, 400) == 0 { - Some(SpriteKind::Scarecrow) - } else { - None - } - }); - } - } else if roll(0, 20) == 0 { - surface_sprite = Some(SpriteKind::ShortGrass); - } else if roll(1, 30) == 0 { - surface_sprite = Some(SpriteKind::MediumGrass); - } - - Some(if in_furrow { dirt } else { mound }) - }, - _ => None, - }; - - if let Some(color) = color { - let is_path = col_sample - .path - .map(|(dist, _, path, _)| dist < path.width) - .unwrap_or(false); - - if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) && !is_path - { - let diff = (surface_z - land_surface_z).abs(); - - for z in -8 - diff..8 + diff { - let pos = Vec3::new(offs.x, offs.y, surface_z + z); - let block = if let Ok(&block) = vol.get(pos) { - // TODO: Figure out whether extra filters are needed. - block - } else { - break; - }; - - if let (0, Some(sprite)) = (z, surface_sprite) { - let _ = vol.set( - pos, - // TODO: Make more principled. - if block.is_fluid() { - block.with_sprite(sprite) - } else { - Block::air(sprite) - }, - ); - } else if z >= 0 { - if [ - BlockKind::Air, - BlockKind::Grass, - BlockKind::Earth, - BlockKind::Sand, - BlockKind::Snow, - BlockKind::Rock, - ] - .contains(&block.kind()) - { - let _ = vol.set(pos, Block::air(SpriteKind::Empty)); - } - } else { - let _ = vol.set( - pos, - Block::new(BlockKind::Earth, noisy_color(color, 4)), - ); - } - } - } - } - } - - // Walls - if let Some((WayKind::Wall, dist, _)) = sample.way { - let color = Lerp::lerp( - Rgb::::from(colors.wall_low).map(i32::from), - Rgb::::from(colors.wall_high).map(i32::from), - (RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0, - ) - .map(|e| (e % 256) as u8); - - let z_offset = if let Some(water_dist) = col_sample.water_dist { - // Water gate - ((water_dist.max(0.0) * 0.45).min(f32::consts::PI).cos() + 1.0) * 4.0 - } else { - 0.0 - } as i32; - - for z in z_offset..12 { - if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) { - let _ = vol.set( - Vec3::new(offs.x, offs.y, surface_z + z), - Block::new(BlockKind::Wood, color), - ); - } - } - } - - // Towers - if let Some((Tower::Wall, _pos)) = sample.tower { - for z in -2..16 { - let _ = vol.set( - Vec3::new(offs.x, offs.y, surface_z + z), - Block::new(BlockKind::Rock, colors.tower_color.into()), - ); - } - } - } - } - - // Apply structures - for structure in &self.structures { - let bounds = structure.bounds_2d(); - - let this_aabr = Aabr { - min: wpos2d - self.origin, - max: wpos2d - self.origin + vol.size_xy().map(|e| e as i32 + 1), - }; - // Skip this structure if it's not near this chunk - if !bounds.collides_with_aabr(this_aabr) { - continue; - } - - let mut bounds = structure.bounds(); - bounds.intersect(Aabb { - min: this_aabr.min.with_z(bounds.min.z), - max: this_aabr.max.with_z(bounds.max.z), - }); - - for x in bounds.min.x..bounds.max.x + 1 { - for y in bounds.min.y..bounds.max.y + 1 { - let col = if let Some(col) = get_column(self.origin + Vec2::new(x, y) - wpos2d) - { - col - } else { - continue; - }; - - for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1 { - let rpos = Vec3::new(x, y, z); - let wpos = Vec3::from(self.origin) + rpos; - let coffs = wpos - Vec3::from(wpos2d); - - if let Some(block) = structure.sample(index, rpos) { - let _ = vol.set(coffs, block); - } - } - } - } - } - } - - pub fn apply_supplement<'a>( - &'a self, - // NOTE: Used only for dynamic elements like chests and entities! - dynamic_rng: &mut impl Rng, - wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample/*<'a>*/>, - supplement: &mut ChunkSupplement, - economy: SiteInformation, - ) { - // let economy: HashMap = SiteInformation::economy - // .values - // .iter() - // .map(|(g, v)| { - // ( - // g, - // ( - // v.unwrap_or(Economy::MINIMUM_PRICE), - // economy.stocks[g] + economy.surplus[g], - // ), - // ) - // }) - // .collect(); - for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { - for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 { - let offs = Vec2::new(x, y); - - let wpos2d = wpos2d + offs; - let rpos = wpos2d - self.origin; - - // Sample terrain - let col_sample = if let Some(col_sample) = get_column(offs) { - col_sample - } else { - continue; - }; - - let sample = self.land.get_at_block(rpos); - - let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0); - - if matches!(sample.plot, Some(Plot::Town { .. })) - && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (20.0 * 40.0)) - { - let is_dummy = - RandomField::new(self.seed + 1).chance(Vec3::from(wpos2d), 1.0 / 15.0); - let entity = if is_dummy { - EntityInfo::at(entity_wpos) - .with_agency(false) - .with_asset_expect("common.entity.village.dummy", dynamic_rng) - } else { - match dynamic_rng.gen_range(0..=4) { - 0 => barnyard(entity_wpos, dynamic_rng), - 1 => bird(entity_wpos, dynamic_rng), - _ => humanoid(entity_wpos, &economy, dynamic_rng), - } - }; - - supplement.add_entity(entity); - } - } - } - } - - pub fn get_color(&self, index: IndexRef, pos: Vec2) -> Option> { - let colors = &index.colors.site.settlement; - - let sample = self.land.get_at_block(pos); - - match sample.plot { - Some(Plot::Dirt) => return Some(colors.plot_dirt.into()), - Some(Plot::Grass) => return Some(colors.plot_grass.into()), - Some(Plot::Water) => return Some(colors.plot_water.into()), - Some(Plot::Town { .. }) => { - return Some( - Rgb::from(colors.plot_town).map2(Rgb::iota(), |e: u8, i: i32| { - e.saturating_add( - (self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8, - ) - .saturating_sub(8) - }), - ); - }, - Some(Plot::Field { seed, .. }) => { - let furrow_dirs = [ - Vec2::new(1, 0), - Vec2::new(0, 1), - Vec2::new(1, 1), - Vec2::new(-1, 1), - ]; - let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; - let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3; - // NOTE: Very hard to understand how to make this dynamically configurable. The - // base values can easily cause the others to go out of range, and there's some - // weird scaling going on. For now, we just let these remain hardcoded. - // - // FIXME: Rewrite this so that validity is not so heavily dependent on the exact - // color values. - return Some(Rgb::new( - if furrow { - 100 - } else { - 32 + seed.to_le_bytes()[0] % 64 - }, - 64 + seed.to_le_bytes()[1] % 128, - 16 + seed.to_le_bytes()[2] % 32, - )); - }, - _ => {}, - } - - None - } -} - -fn barnyard(pos: Vec3, dynamic_rng: &mut impl Rng) -> EntityInfo { - //TODO: use Lottery instead of ad-hoc RNG system - let species = match dynamic_rng.gen_range(0..5) { - 0 => quadruped_small::Species::Pig, - 1 => quadruped_small::Species::Sheep, - 2 => quadruped_small::Species::Goat, - 3 => quadruped_small::Species::Dog, - _ => quadruped_small::Species::Cat, - }; - EntityInfo::at(pos) - .with_body(comp::Body::QuadrupedSmall( - quadruped_small::Body::random_with(dynamic_rng, &species), - )) - .with_alignment(comp::Alignment::Tame) - .with_automatic_name() -} - -fn bird(pos: Vec3, dynamic_rng: &mut impl Rng) -> EntityInfo { - //TODO: use Lottery instead of ad-hoc RNG system - let species = match dynamic_rng.gen_range(0..4) { - 0 => bird_medium::Species::Duck, - 1 => bird_medium::Species::Chicken, - 2 => bird_medium::Species::Goose, - _ => bird_medium::Species::Peacock, - }; - EntityInfo::at(pos) - .with_body(comp::Body::BirdMedium(bird_medium::Body::random_with( - dynamic_rng, - &species, - ))) - .with_alignment(comp::Alignment::Tame) - .with_automatic_name() -} - -fn humanoid(pos: Vec3, economy: &SiteInformation, dynamic_rng: &mut impl Rng) -> EntityInfo { - let entity = EntityInfo::at(pos); - match dynamic_rng.gen_range(0..8) { - 0 | 1 => entity - .with_agent_mark(agent::Mark::Guard) - .with_asset_expect("common.entity.village.guard", dynamic_rng), - 2 => entity - .with_agent_mark(agent::Mark::Merchant) - .with_economy(economy) - .with_lazy_loadout(merchant_loadout) - .with_asset_expect("common.entity.village.merchant", dynamic_rng), - _ => entity.with_asset_expect("common.entity.village.villager", dynamic_rng), - } -} - -pub fn merchant_loadout( - loadout_builder: LoadoutBuilder, - economy: Option<&trade::SiteInformation>, -) -> LoadoutBuilder { - let rng = &mut rand::thread_rng(); - - let mut backpack = Item::new_from_asset_expect("common.items.armor.misc.back.backpack"); - let mut bag1 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); - let mut bag2 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); - let mut bag3 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); - let mut bag4 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack"); - let slots = backpack.slots().len() + 4 * bag1.slots().len(); - let mut stockmap: HashMap = economy - .map(|e| e.unconsumed_stock.clone()) - .unwrap_or_default(); - // modify stock for better gameplay - - // TODO: currently econsim spends all its food on population, resulting in none - // for the players to buy; the `.max` is temporary to ensure that there's some - // food for sale at every site, to be used until we have some solution like NPC - // houses as a limit on econsim population growth - stockmap - .entry(Good::Food) - .and_modify(|e| *e = e.max(10_000.0)) - .or_insert(10_000.0); - // Reduce amount of potions so merchants do not oversupply potions. - // TODO: Maybe remove when merchants and their inventories are rtsim? - // Note: Likely without effect now that potions are counted as food - stockmap - .entry(Good::Potions) - .and_modify(|e| *e = e.powf(0.25)); - // It's safe to truncate here, because coins clamped to 3000 max - // also we don't really want negative values here - stockmap - .entry(Good::Coin) - .and_modify(|e| *e = e.min(rng.gen_range(1000.0..3000.0))); - // assume roughly 10 merchants sharing a town's stock (other logic for coins) - stockmap - .iter_mut() - .filter(|(good, _amount)| **good != Good::Coin) - .for_each(|(_good, amount)| *amount *= 0.1); - // Fill bags with stuff according to unclaimed stock - let mut wares: Vec = - TradePricing::random_items(&mut stockmap, slots as u32, true, true, 16) - .iter() - .map(|(n, a)| { - let mut i = Item::new_from_asset_expect(n); - i.set_amount(*a) - .map_err(|_| tracing::error!("merchant loadout amount failure")) - .ok(); - i - }) - .collect(); - sort_wares(&mut wares); - transfer(&mut wares, &mut backpack); - transfer(&mut wares, &mut bag1); - transfer(&mut wares, &mut bag2); - transfer(&mut wares, &mut bag3); - transfer(&mut wares, &mut bag4); - - loadout_builder - .with_asset_expect("common.loadout.village.merchant", rng) - .back(Some(backpack)) - .bag(ArmorSlot::Bag1, Some(bag1)) - .bag(ArmorSlot::Bag2, Some(bag2)) - .bag(ArmorSlot::Bag3, Some(bag3)) - .bag(ArmorSlot::Bag4, Some(bag4)) -} - -fn sort_wares(bag: &mut [Item]) { - use common::comp::item::TagExampleInfo; - - bag.sort_by(|a, b| { - a.quality() - .cmp(&b.quality()) - // sort by kind - .then( - Ord::cmp( - a.tags().first().map_or("", |tag| tag.name()), - b.tags().first().map_or("", |tag| tag.name()), - ) - ) - // sort by name - .then(Ord::cmp(&a.name(), &b.name())) - }); -} - -fn transfer(wares: &mut Vec, bag: &mut Item) { - let capacity = bag.slots().len(); - for (s, w) in bag - .slots_mut() - .iter_mut() - .zip(wares.drain(0..wares.len().min(capacity))) - { - *s = Some(w); - } -} - -#[derive(Copy, Clone, PartialEq)] -pub enum Crop { - Corn, - Wheat, - Cabbage, - Pumpkin, - Flax, - Carrot, - Tomato, - Radish, - Turnip, - Sunflower, -} - -// NOTE: No support for struct variants in make_case_elim yet, unfortunately, so -// we can't use it. -#[derive(Copy, Clone, PartialEq)] -pub enum Plot { - Hazard, - Dirt, - Grass, - Water, - Town { - district: Option>, - }, - Field { - farm: Id, - seed: u32, - crop: Crop, - }, -} - -const CARDINALS: [Vec2; 4] = [ - Vec2::new(0, 1), - Vec2::new(1, 0), - Vec2::new(0, -1), - Vec2::new(-1, 0), -]; - -#[derive(Copy, Clone, PartialEq)] -pub enum WayKind { - Path, - #[allow(dead_code)] - Wall, -} - -impl WayKind { - pub fn width(&self) -> f32 { - match self { - WayKind::Path => 4.0, - WayKind::Wall => 3.0, - } - } -} - -#[derive(Copy, Clone, PartialEq)] -pub enum Tower { - #[allow(dead_code)] - Wall, -} - -impl Tower { - pub fn radius(&self) -> f32 { - match self { - Tower::Wall => 6.0, - } - } -} - -pub struct Tile { - plot: Id, - ways: [Option; 4], - tower: Option, -} - -impl Tile { - pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) } -} - -#[derive(Default)] -pub struct Sample<'a> { - plot: Option<&'a Plot>, - way: Option<(&'a WayKind, f32, Vec2)>, - tower: Option<(&'a Tower, Vec2)>, - edge_dist: f32, - second_closest: Vec2, -} - -pub struct Land { - /// We use this hasher (FxHasher64) because - /// (1) we need determinism across computers (ruling out AAHash); - /// (2) we don't care about DDOS attacks (ruling out SipHash); - /// (3) we have 8-byte keys (for which FxHash is fastest). - tiles: HashMap, Tile, BuildHasherDefault>, - plots: Store, - sampler_warp: StructureGen2d, - hazard: Id, -} - -impl Land { - pub fn new(rng: &mut impl Rng) -> Self { - let mut plots = Store::default(); - let hazard = plots.insert(Plot::Hazard); - Self { - tiles: HashMap::default(), - plots, - sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5), - hazard, - } - } - - pub fn get_at_block(&self, pos: Vec2) -> Sample { - let mut sample = Sample::default(); - - let neighbors = self.sampler_warp.get(pos); - let closest = neighbors - .iter() - .min_by_key(|(center, _)| center.distance_squared(pos)) - .unwrap() - .0; - let second_closest = neighbors - .iter() - .filter(|(center, _)| *center != closest) - .min_by_key(|(center, _)| center.distance_squared(pos)) - .unwrap() - .0; - sample.second_closest = second_closest.map(to_tile); - sample.edge_dist = (second_closest - pos).map(|e| e as f32).magnitude() - - (closest - pos).map(|e| e as f32).magnitude(); - - let center_tile = self.tile_at(neighbors[4].0.map(to_tile)); - - if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) { - if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powi(2) { - sample.tower = Some((tower, neighbors[4].0)); - } - } - - for (i, _) in CARDINALS.iter().enumerate() { - let map = [1, 5, 7, 3]; - let line = LineSegment2 { - start: neighbors[4].0.map(|e| e as f32), - end: neighbors[map[i]].0.map(|e| e as f32), - }; - if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { - let proj_point = line.projected_point(pos.map(|e| e as f32)); - let dist = proj_point.distance(pos.map(|e| e as f32)); - if dist < way.width() { - sample.way = sample - .way - .filter(|(_, d, _)| *d < dist) - .or(Some((way, dist, proj_point))); - } - } - } - - sample.plot = self.plot_at(closest.map(to_tile)); - - sample - } - - pub fn tile_at(&self, pos: Vec2) -> Option<&Tile> { self.tiles.get(&pos) } - - #[allow(dead_code)] - pub fn tile_at_mut(&mut self, pos: Vec2) -> Option<&mut Tile> { self.tiles.get_mut(&pos) } - - pub fn plot(&self, id: Id) -> &Plot { self.plots.get(id) } - - pub fn plot_at(&self, pos: Vec2) -> Option<&Plot> { - self.tiles.get(&pos).map(|tile| self.plots.get(tile.plot)) - } - - #[allow(dead_code)] - pub fn plot_at_mut(&mut self, pos: Vec2) -> Option<&mut Plot> { - self.tiles - .get(&pos) - .map(|tile| tile.plot) - .map(move |plot| self.plots.get_mut(plot)) - } - - pub fn set(&mut self, pos: Vec2, plot: Id) { - self.tiles.insert(pos, Tile { - plot, - ways: [None; 4], - tower: None, - }); - } - - fn find_tile_near( - &self, - origin: Vec2, - mut match_fn: impl FnMut(Option<&Plot>) -> bool, - ) -> Option> { - Spiral2d::new() - .map(|pos| origin + pos) - .find(|pos| match_fn(self.plot_at(*pos))) - } - - #[allow(dead_code)] - fn find_tile_dir( - &self, - origin: Vec2, - dir: Vec2, - mut match_fn: impl FnMut(Option<&Plot>) -> bool, - ) -> Option> { - (0..) - .map(|i| origin + dir * i) - .find(|pos| match_fn(self.plot_at(*pos))) - } - - fn find_path( - &self, - origin: Vec2, - dest: Vec2, - mut path_cost_fn: impl FnMut(Option<&Tile>, Option<&Tile>) -> f32, - ) -> Option>> { - let heuristic = |pos: &Vec2| (pos - dest).map(|e| e as f32).magnitude(); - let neighbors = |pos: &Vec2| { - let pos = *pos; - CARDINALS.iter().map(move |dir| pos + *dir) - }; - let transition = - |from: &Vec2, to: &Vec2| path_cost_fn(self.tile_at(*from), self.tile_at(*to)); - let satisfied = |pos: &Vec2| *pos == dest; - - // We use this hasher (FxHasher64) because - // (1) we don't care about DDOS attacks (ruling out SipHash); - // (2) we don't care about determinism across computers (we could use AAHash); - // (3) we have 8-byte keys (for which FxHash is fastest). - Astar::new( - 250, - origin, - heuristic, - BuildHasherDefault::::default(), - ) - .poll(250, heuristic, neighbors, transition, satisfied) - .into_path() - } - - /// We use this hasher (FxHasher64) because - /// (1) we don't care about DDOS attacks (ruling out SipHash); - /// (2) we care about determinism across computers (ruling out AAHash); - /// (3) we have 8-byte keys (for which FxHash is fastest). - fn grow_from( - &self, - start: Vec2, - max_size: usize, - _rng: &mut impl Rng, - mut match_fn: impl FnMut(Option<&Plot>) -> bool, - ) -> HashSet, BuildHasherDefault> { - let mut open = VecDeque::new(); - open.push_back(start); - // We use this hasher (FxHasher64) because - // (1) we don't care about DDOS attacks (ruling out SipHash); - // (2) we care about determinism across computers (ruling out AAHash); - // (3) we have 8-byte keys (for which FxHash is fastest). - let mut closed = HashSet::with_hasher(BuildHasherDefault::::default()); - - while open.len() + closed.len() < max_size { - let next_pos = if let Some(next_pos) = open.pop_front() { - closed.insert(next_pos); - next_pos - } else { - break; - }; - - let dirs = [ - Vec2::new(1, 0), - Vec2::new(-1, 0), - Vec2::new(0, 1), - Vec2::new(0, -1), - ]; - - for dir in dirs.iter() { - let neighbor = next_pos + dir; - if !closed.contains(&neighbor) && match_fn(self.plot_at(neighbor)) { - open.push_back(neighbor); - } - } - } - - closed.into_iter().chain(open.into_iter()).collect() - } - - fn write_path( - &mut self, - tiles: &[Vec2], - kind: WayKind, - mut permit_fn: impl FnMut(&Plot) -> bool, - overwrite: bool, - ) { - for tiles in tiles.windows(2) { - let dir = tiles[1] - tiles[0]; - let idx = if dir.y > 0 { - 1 - } else if dir.x > 0 { - 2 - } else if dir.y < 0 { - 3 - } else if dir.x < 0 { - 0 - } else { - continue; - }; - if self.tile_at(tiles[0]).is_none() { - self.set(tiles[0], self.hazard); - } - let plots = &self.plots; - - self.tiles - .get_mut(&tiles[1]) - .filter(|tile| permit_fn(plots.get(tile.plot))) - .map(|tile| { - if overwrite || tile.ways[(idx + 2) % 4].is_none() { - tile.ways[(idx + 2) % 4] = Some(kind); - } - }); - self.tiles - .get_mut(&tiles[0]) - .filter(|tile| permit_fn(plots.get(tile.plot))) - .map(|tile| { - if overwrite || tile.ways[idx].is_none() { - tile.ways[idx] = Some(kind); - } - }); - } - } - - pub fn new_plot(&mut self, plot: Plot) -> Id { self.plots.insert(plot) } -} diff --git a/world/src/site/settlement/town.rs b/world/src/site/settlement/town.rs deleted file mode 100644 index 6de3331710..0000000000 --- a/world/src/site/settlement/town.rs +++ /dev/null @@ -1,82 +0,0 @@ -use super::{GenCtx, AREA_SIZE}; -use common::store::Store; -use rand::prelude::*; -use vek::*; - -pub struct Town { - pub base_tile: Vec2, - radius: i32, - districts: Store, -} - -impl Town { - pub fn districts(&self) -> &Store { &self.districts } - - pub fn generate(origin: Vec2, base_tile: Vec2, ctx: &mut GenCtx) -> Self { - let mut this = Self { - base_tile, - radius: 4, - districts: Store::default(), - }; - - this.generate_districts(origin, ctx); - - this - } - - fn generate_districts(&mut self, origin: Vec2, ctx: &mut GenCtx) { - let base_aabr = Aabr { - min: self.base_tile - self.radius, - max: self.base_tile + self.radius, - }; - - gen_plot(base_aabr, ctx).for_each(base_aabr, &mut |aabr| { - if aabr.center().distance_squared(self.base_tile) < self.radius.pow(2) { - self.districts.insert(District { - seed: ctx.rng.gen(), - aabr, - alt: ctx - .sim - .and_then(|sim| { - sim.get_alt_approx( - origin + aabr.center() * AREA_SIZE as i32 + AREA_SIZE as i32 / 2, - ) - }) - .unwrap_or(0.0) as i32, - }); - } - }); - } -} - -pub struct District { - pub seed: u32, - pub aabr: Aabr, - pub alt: i32, -} - -enum Plot { - District, - Parent(Vec<(Aabr, Plot)>), -} - -impl Plot { - fn for_each(&self, aabr: Aabr, f: &mut impl FnMut(Aabr)) { - match self { - Plot::District => f(aabr), - Plot::Parent(children) => children.iter().for_each(|(aabr, p)| p.for_each(*aabr, f)), - } - } -} - -fn gen_plot(aabr: Aabr, ctx: &mut GenCtx) -> Plot { - if aabr.size().product() <= 9 { - Plot::District - } else if aabr.size().w < aabr.size().h { - let [a, b] = aabr.split_at_y(aabr.min.y + ctx.rng.gen_range(1..aabr.size().h)); - Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))]) - } else { - let [a, b] = aabr.split_at_x(aabr.min.x + ctx.rng.gen_range(1..aabr.size().w)); - Plot::Parent(vec![(a, gen_plot(a, ctx)), (b, gen_plot(b, ctx))]) - } -} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index 7c3c741bcc..c05ccc017a 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -21,7 +21,7 @@ pub use self::{ unit_chooser::UnitChooser, }; -pub use common::grid::Grid; +pub use common::grid::{Grid, RowGen}; use fxhash::FxHasher32; use hashbrown::{HashMap, HashSet}; diff --git a/world/src/util/sampler.rs b/world/src/util/sampler.rs index 0ca094211f..275a6ce720 100644 --- a/world/src/util/sampler.rs +++ b/world/src/util/sampler.rs @@ -5,9 +5,9 @@ pub trait Sampler<'a, 'b>: Sized { fn get(&'b self, index: Self::Index) -> Self::Sample; } -pub trait SamplerMut<'a>: Sized { +pub trait SamplerMut<'a, 'b>: Sized { type Index: 'a; type Sample: 'a; - fn get(&mut self, index: Self::Index) -> Self::Sample; + fn get(&'b mut self, index: Self::Index) -> Self::Sample; }