diff --git a/CHANGELOG.md b/CHANGELOG.md index f0267f7e85..1b8e84896b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dropped items now merge dynamically (including non-stackables). - You no longer need to unlock health, energy and roll skills to get to max. - Rolls now don't skip recovery, and instead have increased buildup during ability interrupts. +- Changed default world map ### Removed diff --git a/assets/world/map/veloren_0_16_0_0.bin b/assets/world/map/veloren_0_16_0_0.bin new file mode 100644 index 0000000000..b90fdcad55 --- /dev/null +++ b/assets/world/map/veloren_0_16_0_0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3319df27b313530a772aa55f78aeabc2a9fa37047bc7c20a22f8dee213fb0ac8 +size 16777252 diff --git a/common/src/terrain/biome.rs b/common/src/terrain/biome.rs index c40956863f..93f6a9c1a4 100644 --- a/common/src/terrain/biome.rs +++ b/common/src/terrain/biome.rs @@ -17,3 +17,37 @@ pub enum BiomeKind { Savannah, Taiga, } + +impl BiomeKind { + /// Roughly represents the difficulty of a biome (value between 1 and 5) + pub fn difficulty(&self) -> i32 { + match self { + BiomeKind::Void => 1, + BiomeKind::Lake => 1, + BiomeKind::Grassland => 2, + BiomeKind::Ocean => 1, + BiomeKind::Mountain => 1, + BiomeKind::Snowland => 2, + BiomeKind::Desert => 5, + BiomeKind::Swamp => 2, + BiomeKind::Jungle => 3, + BiomeKind::Forest => 1, + BiomeKind::Savannah => 2, + BiomeKind::Taiga => 2, + } + } +} + +#[cfg(test)] +#[test] +fn test_biome_difficulty() { + use strum::IntoEnumIterator; + + for biome_kind in BiomeKind::iter() { + assert!( + (1..=5).contains(&biome_kind.difficulty()), + "Biome {biome_kind:?} has invalid difficulty {}", + biome_kind.difficulty() + ); + } +} diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index d2e0a5b355..7c96093cab 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -316,17 +316,7 @@ impl Data { // Try a few times to find a location that's not underwater if let Some((wpos, chunk)) = (0..100) .map(|_| world.sim().get_size().map(|sz| rng.gen_range(0..sz as i32))) - .find_map(|pos| { - Some(( - pos, - world - .sim() - .get(pos) - // This is currently a workaround to force Frost Gigas spawning in cold areas - // TODO: Once more Gigas are implemented remove this - .filter(|c| !c.is_underwater() && c.temp < CONFIG.snow_temp)?, - )) - }) + .find_map(|pos| Some((pos, world.sim().get(pos).filter(|c| !c.is_underwater())?))) .map(|(pos, chunk)| { let wpos2d = pos.cpos_to_wpos_center(); ( diff --git a/server/src/lib.rs b/server/src/lib.rs index 2d0c06848b..f2a914de28 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -146,7 +146,7 @@ use crate::{chat::ChatCache, persistence::character_loader::CharacterScreenRespo use common::comp::Anchor; #[cfg(feature = "worldgen")] pub use world::{ - sim::{FileOpts, GenOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, GenOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, IndexOwned, World, }; diff --git a/server/src/settings.rs b/server/src/settings.rs index 24d07c3c85..880df37062 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -30,11 +30,10 @@ use std::{ path::{Path, PathBuf}, }; use tracing::{error, warn}; -use world::sim::FileOpts; +use world::sim::{FileOpts, DEFAULT_WORLD_SEED}; use self::server_description::ServerDescription; -const DEFAULT_WORLD_SEED: u32 = 230; const CONFIG_DIR: &str = "server_config"; const SETTINGS_FILENAME: &str = "settings.ron"; const WHITELIST_FILENAME: &str = "whitelist.ron"; diff --git a/voxygen/benches/meshing_benchmark.rs b/voxygen/benches/meshing_benchmark.rs index 4b78cb5db5..2cfe9563a1 100644 --- a/voxygen/benches/meshing_benchmark.rs +++ b/voxygen/benches/meshing_benchmark.rs @@ -15,7 +15,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let pool = rayon::ThreadPoolBuilder::new().build().unwrap(); // Generate chunks here to test let (world, index) = World::generate( - 42, + sim::DEFAULT_WORLD_SEED, sim::WorldOpts { // NOTE: If this gets too expensive, we can turn it off. // TODO: Consider an option to turn off all erosion as well, or even provide altitude diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index c9f58c73f9..30d0456583 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -32,6 +32,7 @@ use common::{ }; use common_net::msg::world_msg::{SiteId, SiteInfo}; use i18n::{Localization, LocalizationHandle}; +use rand::{thread_rng, Rng}; //ImageFrame, Tooltip, use crate::settings::Settings; //use std::time::Duration; @@ -194,7 +195,7 @@ enum Mode { /// mode as opposed to create mode. // TODO: Something less janky? Express the problem domain better! character_id: Option, - start_site_idx: usize, + start_site_idx: Option, }, } @@ -248,7 +249,7 @@ impl Mode { prev_starting_site_button: Default::default(), next_starting_site_button: Default::default(), character_id: None, - start_site_idx: 0, + start_site_idx: None, } } @@ -278,7 +279,7 @@ impl Mode { prev_starting_site_button: Default::default(), next_starting_site_button: Default::default(), character_id: Some(character_id), - start_site_idx: 0, + start_site_idx: None, } } } @@ -1362,10 +1363,12 @@ impl Controls { //TODO: Add text-outline here whenever we updated iced to a version supporting // this - let map = if let Some(info) = self.possible_starting_sites.get(*start_site_idx) + let map = if let Some(info) = self + .possible_starting_sites + .get(start_site_idx.unwrap_or_default()) { let site_name = Text::new( - self.possible_starting_sites[*start_site_idx] + self.possible_starting_sites[start_site_idx.unwrap_or_default()] .name .as_deref() .unwrap_or("Unknown"), @@ -1405,12 +1408,16 @@ impl Controls { if self.possible_starting_sites.is_empty() { vec![map] } else { + let selected = start_site_idx.get_or_insert_with(|| { + thread_rng().gen_range(0..self.possible_starting_sites.len()) + }); + let site_slider = starter_slider( i18n.get_msg("char_selection-starting_site").into_owned(), 30, &mut sliders.starting_site, self.possible_starting_sites.len() as u32 - 1, - *start_site_idx as u32, + *selected as u32, |x| Message::StartingSite(x as usize), imgs, ); @@ -1801,7 +1808,7 @@ impl Controls { body: comp::Body::Humanoid(*body), start_site: self .possible_starting_sites - .get(*start_site_idx) + .get(start_site_idx.unwrap_or_default()) .map(|info| info.id), }); self.mode = Mode::select(Some(InfoContent::CreatingCharacter)); @@ -1863,7 +1870,6 @@ impl Controls { //Todo: Add species and body type to randomization. Message::RandomizeCharacter => { if let Mode::CreateOrEdit { body, .. } = &mut self.mode { - use rand::Rng; let body_type = body.body_type; let species = body.species; let mut rng = rand::thread_rng(); @@ -1930,24 +1936,30 @@ impl Controls { }, Message::StartingSite(idx) => { if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode { - *start_site_idx = idx; + *start_site_idx = Some(idx); } }, Message::PrevStartingSite => { if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode { if !self.possible_starting_sites.is_empty() { - *start_site_idx = (*start_site_idx + self.possible_starting_sites.len() - - 1) - % self.possible_starting_sites.len(); + *start_site_idx = Some( + (start_site_idx.unwrap_or_default() + + self.possible_starting_sites.len() + - 1) + % self.possible_starting_sites.len(), + ); } } }, Message::NextStartingSite => { if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode { if !self.possible_starting_sites.is_empty() { - *start_site_idx = - (*start_site_idx + self.possible_starting_sites.len() + 1) - % self.possible_starting_sites.len(); + *start_site_idx = Some( + (start_site_idx.unwrap_or_default() + + self.possible_starting_sites.len() + + 1) + % self.possible_starting_sites.len(), + ); } } }, diff --git a/voxygen/src/singleplayer/singleplayer_world.rs b/voxygen/src/singleplayer/singleplayer_world.rs index a182ce4ee0..cddfe15a93 100644 --- a/voxygen/src/singleplayer/singleplayer_world.rs +++ b/voxygen/src/singleplayer/singleplayer_world.rs @@ -6,7 +6,7 @@ use std::{ use common::{assets::ASSETS_PATH, consts::DAY_LENGTH_DEFAULT}; use serde::{Deserialize, Serialize}; -use server::{FileOpts, GenOpts, DEFAULT_WORLD_MAP}; +use server::{FileOpts, GenOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}; use tracing::error; #[derive(Clone, Deserialize, Serialize)] @@ -84,7 +84,7 @@ fn migrate_old_singleplayer(from: &Path, to: &Path) { return; } - let mut seed = 0; + let mut seed = DEFAULT_WORLD_SEED; let mut day_length = DAY_LENGTH_DEFAULT; let (map_file, gen_opts) = fs::read_to_string(to.join("server_config/settings.ron")) .ok() @@ -238,7 +238,7 @@ impl SingleplayerWorlds { name: "New World".to_string(), gen_opts: None, day_length: DAY_LENGTH_DEFAULT, - seed: 0, + seed: DEFAULT_WORLD_SEED, is_generated: false, map_path: path.join("map.bin"), path, diff --git a/world/benches/cave.rs b/world/benches/cave.rs index 25b9c71e04..786149b452 100644 --- a/world/benches/cave.rs +++ b/world/benches/cave.rs @@ -3,14 +3,14 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rayon::ThreadPoolBuilder; use veloren_world::{ layer, - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, CanvasInfo, Land, World, }; fn cave(c: &mut Criterion) { let pool = ThreadPoolBuilder::new().build().unwrap(); let (world, index) = World::generate( - 230, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/cave_biomes.rs b/world/examples/cave_biomes.rs index 26fa889d36..90a17dd53c 100644 --- a/world/examples/cave_biomes.rs +++ b/world/examples/cave_biomes.rs @@ -6,14 +6,14 @@ use veloren_world::{ self, cave::{Biome, LAYERS}, }, - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, CanvasInfo, Land, World, }; fn main() { let pool = ThreadPoolBuilder::new().build().unwrap(); let (world, index) = World::generate( - 230, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/chunk_compression_benchmarks.rs b/world/examples/chunk_compression_benchmarks.rs index 54f2038f90..ae60471a54 100644 --- a/world/examples/chunk_compression_benchmarks.rs +++ b/world/examples/chunk_compression_benchmarks.rs @@ -29,7 +29,7 @@ use tracing::{debug, trace}; use vek::*; use veloren_world::{ civ::SiteKind, - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, World, }; @@ -682,7 +682,7 @@ fn main() { common_frontend::init_stdout(None); println!("Loading world"); let (world, index) = World::generate( - 59686, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/dungeon_voxel_export.rs b/world/examples/dungeon_voxel_export.rs index f0280c8e1f..695c5f0f24 100644 --- a/world/examples/dungeon_voxel_export.rs +++ b/world/examples/dungeon_voxel_export.rs @@ -12,7 +12,7 @@ use common::{ use rayon::ThreadPoolBuilder; use vek::{Vec2, Vec3}; use veloren_world::{ - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, site2::{plot::PlotKind, Fill, Structure}, CanvasInfo, Land, World, }; @@ -25,7 +25,7 @@ fn main() -> Result { let pool = ThreadPoolBuilder::new().build().unwrap(); println!("Loading world"); let (world, index) = World::generate( - 59686, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/heightmap_visualization.rs b/world/examples/heightmap_visualization.rs index 22e42b4e01..3b5ed27b29 100644 --- a/world/examples/heightmap_visualization.rs +++ b/world/examples/heightmap_visualization.rs @@ -6,7 +6,7 @@ use rayon::ThreadPoolBuilder; use std::{fs::File, io::Write}; use vek::*; use veloren_world::{ - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, Land, World, }; @@ -120,7 +120,7 @@ fn main() { let pool = ThreadPoolBuilder::new().build().unwrap(); println!("Loading world"); let (world, _index) = World::generate( - 59686, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/pricing_csv.rs b/world/examples/pricing_csv.rs index 26dd357d0f..e305c29c1e 100644 --- a/world/examples/pricing_csv.rs +++ b/world/examples/pricing_csv.rs @@ -9,7 +9,7 @@ use strum::IntoEnumIterator; use vek::Vec2; use veloren_world::{ index::Index, - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, World, }; @@ -162,7 +162,7 @@ fn main() { println!("Loading world"); let pool = ThreadPoolBuilder::new().build().unwrap(); let (world, index) = World::generate( - 59686, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/water.rs b/world/examples/water.rs index 4b2475713a..ebbf4a3f9a 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -15,7 +15,7 @@ use tracing_subscriber::{ }; use vek::*; use veloren_world::{ - sim::{self, get_horizon_map, sample_pos, sample_wpos, WorldOpts}, + sim::{self, get_horizon_map, sample_pos, sample_wpos, WorldOpts, DEFAULT_WORLD_SEED}, util::Sampler, ColumnSample, World, CONFIG, }; @@ -43,7 +43,7 @@ fn main() { _map_file.push(map_file); let (world, index) = World::generate( - 5284, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: false, world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/world_block_statistics.rs b/world/examples/world_block_statistics.rs index 551bfa688d..6d597e5905 100644 --- a/world/examples/world_block_statistics.rs +++ b/world/examples/world_block_statistics.rs @@ -21,7 +21,7 @@ use std::{ }; use vek::*; use veloren_world::{ - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, World, }; @@ -63,7 +63,7 @@ fn generate(db_path: &str, ymin: Option, ymax: Option) -> Result<(), B println!("Loading world"); let pool = ThreadPoolBuilder::new().build().unwrap(); let (world, index) = World::generate( - 59686, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/examples/world_generate_time.rs b/world/examples/world_generate_time.rs index 7292a2cf32..bcbd622a23 100644 --- a/world/examples/world_generate_time.rs +++ b/world/examples/world_generate_time.rs @@ -1,6 +1,6 @@ use std::time::Instant; use veloren_world::{ - sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED}, World, }; @@ -9,7 +9,7 @@ fn main() { let start = Instant::now(); let (world, index) = World::generate( - 0, + DEFAULT_WORLD_SEED, WorldOpts { seed_elements: true, // Load default map from assets. diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 2cb667da7a..45aeaa98c8 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -278,7 +278,7 @@ impl Civs { let world_dims = ctx.sim.get_aabr(); for _ in 0..initial_civ_count * 3 { attempt(5, || { - let (loc, kind) = match ctx.rng.gen_range(0..115) { + let (loc, kind) = match ctx.rng.gen_range(0..90) { 0..=4 => { if index.features().site2_giant_trees { ( @@ -304,7 +304,7 @@ impl Civs { ) } }, - 5..=10 => ( + 5..=15 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -314,7 +314,7 @@ impl Civs { )?, SiteKind::Gnarling, ), - 11..=16 => ( + 16..=20 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -324,7 +324,7 @@ impl Civs { )?, SiteKind::ChapelSite, ), - 17..=22 => ( + 21..=27 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -334,7 +334,7 @@ impl Civs { )?, SiteKind::Adlet, ), - 23..=35 => ( + 28..=38 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -344,7 +344,7 @@ impl Civs { )?, SiteKind::PirateHideout, ), - 36..=42 => ( + 39..=45 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -354,7 +354,7 @@ impl Civs { )?, SiteKind::JungleRuin, ), - 43..=49 => ( + 46..=55 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -364,7 +364,7 @@ impl Civs { )?, SiteKind::RockCircle, ), - 50..=59 => ( + 56..=66 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -374,7 +374,7 @@ impl Civs { )?, SiteKind::TrollCave, ), - 60..=69 => ( + 67..=72 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -384,7 +384,7 @@ impl Civs { )?, SiteKind::Camp, ), - 70..=74 => ( + 73..=76 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -394,7 +394,7 @@ impl Civs { )?, SiteKind::Haniwa, ), - 75..=85 => ( + 77..=79 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -404,7 +404,7 @@ impl Civs { )?, SiteKind::Terracotta, ), - /*86..=91 => ( + /*80..=86 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -414,7 +414,7 @@ impl Civs { )?, SiteKind::DwarvenMine, ), - 92..=97 => ( + 86..=97 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() @@ -945,10 +945,10 @@ impl Civs { fn birth_civ(&mut self, ctx: &mut GenCtx) -> Option> { // TODO: specify SiteKind based on where a suitable location is found let kind = match ctx.rng.gen_range(0..64) { - 0..=10 => SiteKind::CliffTown, - 11..=12 => SiteKind::DesertCity, - 13..=18 => SiteKind::SavannahPit, - 19..=36 => SiteKind::CoastalTown, + 0..=8 => SiteKind::CliffTown, + 9..=17 => SiteKind::DesertCity, + 18..=23 => SiteKind::SavannahPit, + 24..=33 => SiteKind::CoastalTown, _ => SiteKind::Refactor, }; let world_dims = ctx.sim.get_aabr(); @@ -1558,11 +1558,7 @@ impl Civs { } fn tree_enemies(&self) -> impl Iterator> + '_ { - self.sites().filter_map(|s| match s.kind { - SiteKind::Castle => Some(s.center), - _ if s.is_settlement() => Some(s.center), - _ => None, - }) + self.sites().map(|s| s.center) } fn castle_enemies(&self) -> impl Iterator> + '_ { diff --git a/world/src/lib.rs b/world/src/lib.rs index 4ee85539a5..7a8b1b69bd 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -36,7 +36,7 @@ pub use block::BlockGen; use civ::WorldCivStage; pub use column::ColumnSample; pub use common::terrain::site::{DungeonKindMeta, SettlementKindMeta}; -use common::terrain::CoordinateConversions; +use common::{spiral::Spiral2d, terrain::CoordinateConversions}; pub use index::{IndexOwned, IndexRef}; use sim::WorldSimStage; @@ -249,7 +249,7 @@ impl World { })) .collect(), possible_starting_sites: { - const STARTING_SITE_COUNT: usize = 4; + const STARTING_SITE_COUNT: usize = 5; let mut candidates = self .civs() @@ -258,21 +258,55 @@ impl World { .filter_map(|(_, civ_site)| Some((civ_site, civ_site.site_tmp?))) .map(|(civ_site, site_id)| { // Score the site according to how suitable it is to be a starting site - let mut score = 0.0; - if let SiteKind::Refactor(site2) = &index.sites[site_id].kind { - // Strongly prefer towns - score += 1000.0; - // Prefer sites of a medium size - score += 2.0 / (1.0 + (site2.plots().len() as f32 - 20.0).abs() / 10.0); + let (site2, mut score) = match &index.sites[site_id].kind { + SiteKind::Refactor(site2) => (site2, 2.0), + // Non-town sites should not be chosen as starting sites and get a score of 0 + _ => return (site_id.id(), 0.0) }; - // Prefer sites in hospitable climates - if let Some(chunk) = self.sim().get(civ_site.center) { - score += 1.0 / (1.0 + chunk.temp.abs()); - score += 1.0 / (1.0 + (chunk.humidity - CONFIG.forest_hum).abs() * 2.0); - } + + /// Optimal number of plots in a starter town + const OPTIMAL_STARTER_TOWN_SIZE: f32 = 30.0; + + // Prefer sites of a medium size + let plots = site2.plots().len() as f32; + let size_score = if plots > OPTIMAL_STARTER_TOWN_SIZE { + 1.0 + (1.0 / (1.0 + ((plots - OPTIMAL_STARTER_TOWN_SIZE) / 15.0).powi(3))) + } else { + (2.05 / (1.0 + ((OPTIMAL_STARTER_TOWN_SIZE - plots) / 15.0).powi(5))) - 0.05 + }.max(0.01); + + score *= size_score; + // Prefer sites that are close to the centre of the world - score += 4.0 / (1.0 + civ_site.center.map2(self.sim().get_size(), |e, sz| (e as f32 / sz as f32 - 0.5).abs() * 2.0).reduce_partial_max()); + let pos_score = ( + 10.0 / ( + 1.0 + ( + civ_site.center.map2(self.sim().get_size(), + |e, sz| + (e as f32 / sz as f32 - 0.5).abs() * 2.0).reduce_partial_max() + ).powi(6) * 25.0 + ) + ).max(0.02); + score *= pos_score; + + // Check if neighboring biomes are beginner friendly + let mut chunk_scores = 2.0; + for (chunk, distance) in Spiral2d::with_radius(10) + .filter_map(|rel_pos| { + let chunk_pos = civ_site.center + rel_pos * 2; + self.sim().get(chunk_pos).zip(Some(rel_pos.as_::().magnitude())) + }) + { + let weight = 1.0 / (distance * std::f32::consts::TAU + 1.0); + let chunk_difficulty = 20.0 / (20.0 + chunk.get_biome().difficulty().pow(4) as f32 / 5.0); + // let chunk_difficulty = 1.0 / chunk.get_biome().difficulty() as f32; + + chunk_scores *= 1.0 - weight + chunk_difficulty * weight; + } + + score *= chunk_scores; + (site_id.id(), score) }) .collect::>(); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index ff2acb27b9..eb814e516e 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -560,7 +560,25 @@ pub type ModernMap = WorldMap_0_7_0; /// TODO: Consider using some naming convention to automatically change this /// with changing versions, or at least keep it in a constant somewhere that's /// easy to change. -pub const DEFAULT_WORLD_MAP: &str = "world.map.veloren_0_9_0_0"; +// Generation parameters: +// +// gen_opts: ( +// erosion_quality: 1.0, +// map_kind: Circle, +// scale: 2.098048498703866, +// x_lg: 10, +// y_lg: 10, +// ) +// seed: 469876673 +// +// The biome seed can found below +pub const DEFAULT_WORLD_MAP: &str = "world.map.veloren_0_16_0_0"; +/// This is *not* the seed used to generate the default map, this seed was used +/// to generate a better set of biomes on it as the original ones were +/// unsuitable. +/// +/// See DEFAULT_WORLD_MAP to get the original worldgen parameters. +pub const DEFAULT_WORLD_SEED: u32 = 1948292704; impl WorldFileLegacy { #[inline] diff --git a/world/src/site/economy/context.rs b/world/src/site/economy/context.rs index 9cf07f69ee..54a00bca45 100644 --- a/world/src/site/economy/context.rs +++ b/world/src/site/economy/context.rs @@ -336,7 +336,7 @@ mod tests { execute_with_tracing(Level::INFO, || { let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); info!("init"); - let seed = 59686; + let seed = sim::DEFAULT_WORLD_SEED; let opts = sim::WorldOpts { seed_elements: true, world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), @@ -362,7 +362,7 @@ mod tests { execute_with_tracing(Level::INFO, || { let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); info!("init"); - let seed = 59686; + let seed = sim::DEFAULT_WORLD_SEED; let opts = sim::WorldOpts { seed_elements: true, world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), @@ -502,7 +502,7 @@ mod tests { execute_with_tracing(Level::ERROR, || { let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); info!("init"); - let seed = 59686; + let seed = sim::DEFAULT_WORLD_SEED; let opts = sim::WorldOpts { seed_elements: true, world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),