From f553700e8c409b0f51b3f57ada274fe1509c4d70 Mon Sep 17 00:00:00 2001 From: James Melkonian Date: Mon, 3 May 2021 02:00:23 +0000 Subject: [PATCH] Add map markers for lakes and mountains --- CHANGELOG.md | 2 + .../voxygen/element/ui/map/buttons/peak.png | 3 + .../element/ui/map/buttons/peak_hover.png | 3 + assets/voxygen/i18n/en/hud/map.ron | 1 + client/src/lib.rs | 9 +- common/net/src/msg/world_msg.rs | 15 ++ voxygen/src/hud/diary.rs | 8 +- voxygen/src/hud/img_ids.rs | 2 + voxygen/src/hud/map.rs | 154 ++++++++++++- voxygen/src/hud/mod.rs | 1 + voxygen/src/session/settings_change.rs | 4 + voxygen/src/settings/interface.rs | 6 +- world/src/civ/mod.rs | 214 +++++++++++++++++- world/src/lib.rs | 10 + world/src/sim/mod.rs | 1 + 15 files changed, 419 insertions(+), 14 deletions(-) create mode 100644 assets/voxygen/element/ui/map/buttons/peak.png create mode 100644 assets/voxygen/element/ui/map/buttons/peak_hover.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8739a38c..a808c89659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Specific music tracks can now play exclusively in towns. - Custom map markers can be placed now - Fundamentals/prototype for wiring system +- Mountain peak and lake markers on the map ### Changed @@ -112,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Skillbar buttons correctly account for skill points when checking if player has enough stamina for the ability. - Burning Debuff icon is now displayed correctly. - Villagers in safezones no longer spam messages upon seeing an enemy +- Wolf AI will no longer circle into walls and will instead use the power of raycasts to stop early ## [0.9.0] - 2021-03-20 diff --git a/assets/voxygen/element/ui/map/buttons/peak.png b/assets/voxygen/element/ui/map/buttons/peak.png new file mode 100644 index 0000000000..0b0a8ea411 --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/peak.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71e27df1a5c955b1a15e071efc28b1fd61c018a7b0bfbcc62cc680e45a7fadb9 +size 189 diff --git a/assets/voxygen/element/ui/map/buttons/peak_hover.png b/assets/voxygen/element/ui/map/buttons/peak_hover.png new file mode 100644 index 0000000000..3fdb23c798 --- /dev/null +++ b/assets/voxygen/element/ui/map/buttons/peak_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:531e66f4c13eaef13a6716dcc6b3cf8d44bc15ce995e3fe4b4d866df89b000f0 +size 204 diff --git a/assets/voxygen/i18n/en/hud/map.ron b/assets/voxygen/i18n/en/hud/map.ron index d0ccb38cbb..2b75841070 100644 --- a/assets/voxygen/i18n/en/hud/map.ron +++ b/assets/voxygen/i18n/en/hud/map.ron @@ -13,6 +13,7 @@ "hud.map.dungeons": "Dungeons", "hud.map.caves": "Caves", "hud.map.cave": "Cave", + "hud.map.peaks": "Mountains", "hud.map.trees": "Giant Trees", "hud.map.tree": "Giant Tree", "hud.map.town": "Town", diff --git a/client/src/lib.rs b/client/src/lib.rs index 51c577aaeb..ad321d4c67 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -47,7 +47,7 @@ use common_base::span; use common_net::{ msg::{ self, validate_chat_msg, - world_msg::{EconomyInfo, SiteId, SiteInfo}, + world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo}, ChatMsgValidationError, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, PresenceKind, RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer, @@ -152,6 +152,7 @@ pub struct Client { player_list: HashMap, character_list: CharacterList, sites: HashMap, + pois: Vec, pub chat_mode: ChatMode, recipe_book: RecipeBook, available_recipes: HashMap>, @@ -266,6 +267,7 @@ impl Client { lod_horizon, world_map, sites, + pois, recipe_book, max_group_size, client_timeout, @@ -628,6 +630,7 @@ impl Client { Grid::from_raw(map_size.map(|e| e as i32), lod_horizon), (world_map_layers, map_size, map_bounds), world_map.sites, + world_map.pois, recipe_book, max_group_size, client_timeout, @@ -661,6 +664,7 @@ impl Client { }) }) .collect(), + pois, recipe_book, available_recipes: HashMap::default(), chat_mode: ChatMode::default(), @@ -1036,6 +1040,9 @@ impl Client { /// Unstable, likely to be removed in a future release pub fn sites(&self) -> &HashMap { &self.sites } + /// Unstable, likely to be removed in a future release + pub fn pois(&self) -> &Vec { &self.pois } + pub fn sites_mut(&mut self) -> &mut HashMap { &mut self.sites } pub fn enable_lantern(&mut self) { diff --git a/common/net/src/msg/world_msg.rs b/common/net/src/msg/world_msg.rs index 12b0c46f61..3d67c455a3 100644 --- a/common/net/src/msg/world_msg.rs +++ b/common/net/src/msg/world_msg.rs @@ -123,6 +123,7 @@ pub struct WorldMapMsg { /// (256 possible angles). pub horizons: [(Vec, Vec); 2], pub sites: Vec, + pub pois: Vec, } pub type SiteId = common::trade::SiteId; @@ -156,3 +157,17 @@ pub struct EconomyInfo { pub last_exports: HashMap, pub resources: HashMap, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PoiInfo { + pub kind: PoiKind, + pub wpos: Vec2, + pub name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[repr(u8)] +pub enum PoiKind { + Peak(u32), + Lake(u32), +} diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 8a2561d339..1fefcee57e 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -190,7 +190,6 @@ pub struct Diary<'a> { created_btns_top_r: usize, created_btns_bot_l: usize, created_btns_bot_r: usize, - hovering_exp_bar: bool, } impl<'a> Diary<'a> { @@ -222,7 +221,6 @@ impl<'a> Diary<'a> { created_btns_top_r: 0, created_btns_bot_l: 0, created_btns_bot_r: 0, - hovering_exp_bar: false, } } } @@ -454,11 +452,11 @@ impl<'a> Widget for Diary<'a> { .middle_of(state.exp_bar_bg) .set(state.exp_bar_frame, ui); // Show EXP bar text on hover - self.hovering_exp_bar = ui + if ui .widget_input(state.exp_bar_frame) .mouse() - .map_or(false, |m| m.is_over()); - if self.hovering_exp_bar { + .map_or(false, |m| m.is_over()) + { Text::new(&exp_txt) .mid_top_with_margin_on(state.exp_bar_frame, 47.0) .font_id(self.fonts.cyri.conrod_id) diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 8a47d6eb1e..b74af470be 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -382,6 +382,8 @@ image_ids! { mmap_site_excl: "voxygen.element.ui.map.buttons.excl", mmap_site_tree: "voxygen.element.ui.map.buttons.tree", mmap_site_tree_hover: "voxygen.element.ui.map.buttons.tree_hover", + mmap_poi_peak: "voxygen.element.ui.map.buttons.peak", + mmap_poi_peak_hover: "voxygen.element.ui.map.buttons.peak_hover", // Window Parts window_3: "voxygen.element.ui.generic.frames.window_3", diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index ec02f3a33f..fea6aa9a98 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -1,7 +1,7 @@ use super::{ img_ids::{Imgs, ImgsRot}, Show, QUALITY_COMMON, QUALITY_DEBUG, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE, - TEXT_COLOR, TEXT_GRAY_COLOR, TEXT_VELORITE, UI_HIGHLIGHT_0, UI_MAIN, + TEXT_BG, TEXT_BLUE_COLOR, TEXT_COLOR, TEXT_GRAY_COLOR, TEXT_VELORITE, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ i18n::Localization, @@ -11,7 +11,7 @@ use crate::{ }; use client::{self, Client, SiteInfoRich}; use common::{comp, comp::group::Role, terrain::TerrainChunkSize, trade::Good, vol::RectVolSize}; -use common_net::msg::world_msg::{SiteId, SiteKind}; +use common_net::msg::world_msg::{PoiKind, SiteId, SiteKind}; use conrod_core::{ color, position, widget::{self, Button, Image, Rectangle, Text}, @@ -37,6 +37,11 @@ widget_ids! { qlog_title, zoom_slider, mmap_site_icons[], + mmap_poi_icons[], + mmap_poi_title_bgs[], + mmap_poi_titles[], + peaks_txt, + peaks_txt_bg, site_difs[], member_indicators[], member_height_indicators[], @@ -57,6 +62,9 @@ widget_ids! { show_trees_img, show_trees_box, show_trees_text, + show_peaks_img, + show_peaks_box, + show_peaks_text, show_difficulty_img, show_difficulty_box, show_difficulty_text, @@ -191,6 +199,7 @@ impl<'a> Widget for Map<'a> { let show_castles = self.global_state.settings.interface.map_show_castles; let show_caves = self.global_state.settings.interface.map_show_caves; let show_trees = self.global_state.settings.interface.map_show_trees; + let show_peaks = self.global_state.settings.interface.map_show_peaks; let show_topo_map = self.global_state.settings.interface.map_show_topo_map; let mut events = Vec::new(); let i18n = &self.localized_strings; @@ -371,6 +380,9 @@ impl<'a> Widget for Map<'a> { } // Handle zooming with the mousewheel + + // TODO: Experiment with zooming around cursor position instead of map center + // (issue #1111) let scrolled: f64 = ui .widget_input(state.ids.map_layers[0]) .scrolls() @@ -590,7 +602,61 @@ impl<'a> Widget for Map<'a> { .graphics_for(state.ids.show_trees_box) .color(TEXT_COLOR) .set(state.ids.show_trees_text, ui); + // Peaks + Image::new(self.imgs.mmap_poi_peak) + .down_from(state.ids.show_trees_img, 10.0) + .w_h(20.0, 20.0) + .set(state.ids.show_peaks_img, ui); + if Button::image(if show_peaks { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox + }) + .w_h(18.0, 18.0) + .hover_image(if show_peaks { + self.imgs.checkbox_checked_mo + } else { + self.imgs.checkbox_mo + }) + .press_image(if show_peaks { + self.imgs.checkbox_checked + } else { + self.imgs.checkbox_press + }) + .right_from(state.ids.show_peaks_img, 10.0) + .set(state.ids.show_peaks_box, ui) + .was_clicked() + { + events.push(Event::SettingsChange(MapShowPeaks(!show_peaks))); + } + Text::new(i18n.get("hud.map.peaks")) + .right_from(state.ids.show_peaks_box, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.show_peaks_box) + .color(TEXT_COLOR) + .set(state.ids.show_peaks_text, ui); // Map icons + if state.ids.mmap_poi_icons.len() < self.client.pois().len() { + state.update(|state| { + state + .ids + .mmap_poi_icons + .resize(self.client.pois().len(), &mut ui.widget_id_generator()) + }); + state.update(|state| { + state + .ids + .mmap_poi_titles + .resize(self.client.pois().len(), &mut ui.widget_id_generator()) + }); + state.update(|state| { + state + .ids + .mmap_poi_title_bgs + .resize(self.client.pois().len(), &mut ui.widget_id_generator()) + }); + } if state.ids.mmap_site_icons.len() < self.client.sites().len() { state.update(|state| { state @@ -780,6 +846,90 @@ impl<'a> Widget for Map<'a> { } } } + for (i, poi) in self.client.pois().iter().enumerate() { + let rpos = match wpos_to_rpos(poi.wpos.map(|e| e as f32)) { + Some(rpos) => rpos, + None => continue, + }; + let title = &poi.name; + match poi.kind { + PoiKind::Peak(alt) => { + let height = format!("{} m", alt); + if show_peaks && zoom > 3.0 { + Text::new(title) + .x_y_position_relative_to( + state.ids.map_layers[0], + position::Relative::Scalar(rpos.x as f64), + position::Relative::Scalar(rpos.y as f64 + zoom * 3.0), + ) + .font_size(self.fonts.cyri.scale((zoom * 2.0) as u32)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.map_layers[0]) + .color(TEXT_BG) + .set(state.ids.mmap_poi_title_bgs[i], ui); + Text::new(title) + .bottom_left_with_margins_on(state.ids.mmap_poi_title_bgs[i], 1.0, 1.0) + .font_size(self.fonts.cyri.scale((zoom * 2.0) as u32)) + .font_id(self.fonts.cyri.conrod_id) + //.graphics_for(state.ids.map_layers[0]) + .color(TEXT_COLOR) + .set(state.ids.mmap_poi_titles[i], ui); + // Show peak altitude + if ui + .widget_input(state.ids.mmap_poi_titles[i]) + .mouse() + .map_or(false, |m| m.is_over()) + { + Text::new(&height) + .mid_bottom_with_margin_on( + state.ids.mmap_poi_title_bgs[i], + -zoom * 2.5, + ) + .font_size(self.fonts.cyri.scale((zoom * 2.0) as u32)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.map_layers[0]) + .color(TEXT_BG) + .set(state.ids.peaks_txt_bg, ui); + Text::new(&height) + .bottom_left_with_margins_on(state.ids.peaks_txt_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale((zoom * 2.0) as u32)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.map_layers[0]) + .color(TEXT_COLOR) + .set(state.ids.peaks_txt, ui); + } + } + }, + PoiKind::Lake(size) => { + if zoom.powi(2) * size as f64 > 37.0 { + let font_scale_factor = if size > 20 { + size as f64 / 25.0 + } else if size > 10 { + size as f64 / 10.0 + } else if size > 5 { + size as f64 / 6.0 + } else { + size as f64 / 2.5 + }; + Text::new(&format!("{}", title)) + .x_y_position_relative_to( + state.ids.map_layers[0], + position::Relative::Scalar(rpos.x as f64), + position::Relative::Scalar(rpos.y as f64), + ) + .font_size( + self.fonts.cyri.scale( + (2.0 + font_scale_factor * zoom).min(18.0).max(10.0) as u32, + ), + ) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.map_layers[0]) + .color(TEXT_BLUE_COLOR) + .set(state.ids.mmap_poi_icons[i], ui); + } + }, + } + } // Group member indicators let client_state = self.client.state(); let stats = client_state.ecs().read_storage::(); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 795416fa22..a920132313 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -105,6 +105,7 @@ use vek::*; const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); const TEXT_VELORITE: Color = Color::Rgba(0.0, 0.66, 0.66, 1.0); +const TEXT_BLUE_COLOR: Color = Color::Rgba(0.8, 0.9, 1.0, 1.0); const TEXT_GRAY_COLOR: Color = Color::Rgba(0.5, 0.5, 0.5, 1.0); const TEXT_DULL_RED_COLOR: Color = Color::Rgba(0.56, 0.2, 0.2, 1.0); const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index 3e52b32183..c9f4a87fd6 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -111,6 +111,7 @@ pub enum Interface { MapShowCastles(bool), MapShowCaves(bool), MapShowTrees(bool), + MapShowPeaks(bool), ResetInterfaceSettings, } @@ -454,6 +455,9 @@ impl SettingsChange { Interface::MapShowTrees(map_show_trees) => { settings.interface.map_show_trees = map_show_trees; }, + Interface::MapShowPeaks(map_show_peaks) => { + settings.interface.map_show_peaks = map_show_peaks; + }, Interface::ResetInterfaceSettings => { // Reset Interface Settings let tmp = settings.interface.intro_show; diff --git a/voxygen/src/settings/interface.rs b/voxygen/src/settings/interface.rs index 63cf55ee11..9e2a96472d 100644 --- a/voxygen/src/settings/interface.rs +++ b/voxygen/src/settings/interface.rs @@ -35,6 +35,7 @@ pub struct InterfaceSettings { pub loading_tips: bool, pub map_show_caves: bool, pub map_show_trees: bool, + pub map_show_peaks: bool, pub minimap_show: bool, pub minimap_face_north: bool, pub minimap_zoom: f64, @@ -65,10 +66,11 @@ impl Default for InterfaceSettings { map_show_difficulty: true, map_show_towns: true, map_show_dungeons: true, - map_show_castles: true, + map_show_castles: false, loading_tips: true, map_show_caves: true, - map_show_trees: true, + map_show_trees: false, + map_show_peaks: false, minimap_show: true, minimap_face_north: false, minimap_zoom: 10.0, diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 399f1ee630..077707fd57 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -4,10 +4,10 @@ mod econ; use crate::{ config::CONFIG, - sim::WorldSim, + sim::{RiverKind, WorldSim}, site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite, Tree}, site2, - util::{attempt, seed_expan, NEIGHBORS}, + util::{attempt, seed_expan, CARDINALS, NEIGHBORS}, Index, Land, }; use common::{ @@ -15,12 +15,12 @@ use common::{ path::Path, spiral::Spiral2d, store::{Id, Store}, - terrain::{uniform_idx_as_vec2, MapSizeLg, TerrainChunkSize}, + terrain::{uniform_idx_as_vec2, vec2_as_uniform_idx, MapSizeLg, TerrainChunkSize}, vol::RectVolSize, }; use core::{fmt, hash::BuildHasherDefault, ops::Range}; use fxhash::FxHasher64; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use rand::prelude::*; use rand_chacha::ChaChaRng; use tracing::{debug, info, warn}; @@ -44,6 +44,7 @@ pub struct CaveInfo { pub struct Civs { pub civs: Store, pub places: Store, + pub pois: Store, pub tracks: Store, /// We use this hasher (FxHasher64) because @@ -62,6 +63,7 @@ pub struct Civs { // Change this to get rid of particularly horrid seeds const SEED_SKIP: u8 = 5; +const POI_THINNING_DIST_SQRD: i32 = 300; pub struct GenCtx<'a, R: Rng> { sim: &'a mut WorldSim, @@ -85,6 +87,10 @@ impl Civs { let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); let initial_civ_count = initial_civ_count(sim.map_size_lg()); let mut ctx = GenCtx { sim, rng }; + info!("starting peak naming"); + this.name_peaks(&mut ctx); + info!("starting lake naming"); + this.name_lakes(&mut ctx); for _ in 0..ctx.sim.get_size().product() / 10_000 { this.generate_cave(&mut ctx); @@ -456,6 +462,191 @@ impl Civs { self.places.insert(Place { center: loc }) } + /// Adds lake POIs and names them + fn name_lakes(&mut self, ctx: &mut GenCtx) { + let map_size_lg = ctx.sim.map_size_lg(); + let rng = &mut ctx.rng; + let sim_chunks = &ctx.sim.chunks; + let lakes = sim_chunks + .iter() + .enumerate() + .filter(|(posi, chunk)| { + let neighbor_alts_min = common::terrain::neighbors(map_size_lg, *posi) + .map(|i| sim_chunks[i].alt as u32) + .min(); + chunk + .river + .river_kind + .map_or(false, |r_kind| matches!(r_kind, RiverKind::Lake { .. })) + && neighbor_alts_min.map_or(false, |n_alt| (chunk.alt as u32) < n_alt) + }) + .map(|(posi, chunk)| { + ( + uniform_idx_as_vec2(map_size_lg, posi), + chunk.alt as u32, + chunk.water_alt as u32, + ) + }) + .collect::, u32, u32)>>(); + let mut removals = vec![false; lakes.len()]; + let mut lake_alts = HashSet::new(); + for (i, (loc, alt, water_alt)) in lakes.iter().enumerate() { + for (k, (n_loc, n_alt, n_water_alt)) in lakes.iter().enumerate() { + // If the difference in position of this low point and another is + // below a threshold and this chunk's altitude is higher, remove the + // lake from the list. Also remove shallow ponds. + // If this lake water altitude is already accounted for and the lake bed + // altitude is lower than the neighboring lake bed altitude, remove this + // lake from the list. Otherwise, add this lake water altitude to the list + // of counted lake water altitudes. + if i != k + && (*water_alt <= CONFIG.sea_level as u32 + || (!lake_alts.insert(water_alt) + && water_alt == n_water_alt + && alt > n_alt) + || ((loc).distance_squared(*n_loc) < POI_THINNING_DIST_SQRD + && alt >= n_alt)) + { + // This cannot panic as `removals` is the same length as `lakes` + // i is the index in `lakes` + removals[i] = true; + } + } + } + let mut num_lakes = 0; + for (_j, (loc, alt, water_alt)) in lakes.iter().enumerate().filter(|&(i, _)| !removals[i]) { + // Recenter the location of the lake POI + // Sample every few units to speed this up + let sample_step = 3; + let mut chords: [i32; 4] = [0, 0, 0, 0]; + // only search up to 100 chunks in any direction + for (j, chord) in chords.iter_mut().enumerate() { + for i in 0..100 { + let posi = vec2_as_uniform_idx( + map_size_lg, + Vec2::new(loc.x, loc.y) + CARDINALS[j] * sample_step * i, + ); + if let Some(r_kind) = sim_chunks[posi].river.river_kind { + if matches!(r_kind, RiverKind::Lake { .. }) { + *chord += sample_step; + } else { + break; + } + } else { + break; + } + } + } + let center_y = ((chords[0] + chords[1]) / 2) - chords[1] + loc.y; + let center_x = ((chords[2] + chords[3]) / 2) - chords[3] + loc.x; + let new_loc = Vec2::new(center_x, center_y); + let size_parameter = ((chords[2] + chords[3]) + (chords[0] + chords[1]) / 4) as u32; + let lake = PointOfInterest { + name: { + let name = NameGen::location(rng).generate(); + if size_parameter > 30 { + format!("{} Sea", name) + } else if (water_alt - alt) < 30 { + match rng.gen_range(0..5) { + 0 => format!("{} Shallows", name), + 1 => format!("{} Pool", name), + 2 => format!("{} Well", name), + _ => format!("{} Pond", name), + } + } else { + match rng.gen_range(0..6) { + 0 => format!("{} Lake", name), + 1 => format!("Loch {}", name), + _ => format!("Lake {}", name), + } + } + }, + // Size parameter is based on the east west chord length with a smaller factor from + // the north south chord length. This is used for text scaling on the map + kind: PoiKind::Lake(size_parameter), + loc: new_loc, + }; + num_lakes += 1; + self.pois.insert(lake); + } + info!(?num_lakes, "all lakes named"); + } + + /// Adds mountain POIs and name them + fn name_peaks(&mut self, ctx: &mut GenCtx) { + let map_size_lg = ctx.sim.map_size_lg(); + const MIN_MOUNTAIN_ALT: f32 = 600.0; + const MIN_MOUNTAIN_CHAOS: f32 = 0.35; + let rng = &mut ctx.rng; + let sim_chunks = &ctx.sim.chunks; + let peaks = sim_chunks + .iter() + .enumerate() + .filter(|(posi, chunk)| { + let neighbor_alts_max = common::terrain::neighbors(map_size_lg, *posi) + .map(|i| sim_chunks[i].alt as u32) + .max(); + chunk.alt > MIN_MOUNTAIN_ALT + && chunk.chaos > MIN_MOUNTAIN_CHAOS + && neighbor_alts_max.map_or(false, |n_alt| chunk.alt as u32 > n_alt) + }) + .map(|(posi, chunk)| { + ( + posi, + uniform_idx_as_vec2(map_size_lg, posi), + (chunk.alt - CONFIG.sea_level) as u32, + ) + }) + .collect::, u32)>>(); + let mut num_peaks = 0; + let mut removals = vec![false; peaks.len()]; + for (i, peak) in peaks.iter().enumerate() { + for (k, n_peak) in peaks.iter().enumerate() { + // If the difference in position of this peak and another is + // below a threshold and this peak's altitude is lower, remove the + // peak from the list + if i != k + && (peak.1).distance_squared(n_peak.1) < POI_THINNING_DIST_SQRD + && peak.2 <= n_peak.2 + { + // Remove this peak + // This cannot panic as `removals` is the same length as `peaks` + // i is the index in `peaks` + removals[i] = true; + } + } + } + peaks + .iter() + .enumerate() + .filter(|&(i, _)| !removals[i]) + .for_each(|(_, (_, loc, alt))| { + num_peaks += 1; + self.pois.insert(PointOfInterest { + name: { + let name = NameGen::location(rng).generate(); + if *alt < 1000 { + match rng.gen_range(0..6) { + 0 => format!("{} Bluff", name), + 1 => format!("{} Crag", name), + _ => format!("{} Hill", name), + } + } else { + match rng.gen_range(0..8) { + 0 => format!("{}'s Peak", name), + 1 => format!("{} Peak", name), + 2 => format!("{} Summit", name), + _ => format!("Mount {}", name), + } + } + }, + kind: PoiKind::Peak(*alt), + loc: *loc, + }); + }); + info!(?num_peaks, "all peaks named"); + } + fn establish_site( &mut self, ctx: &mut GenCtx, @@ -726,3 +917,18 @@ impl Site { pub fn is_castle(&self) -> bool { matches!(self.kind, SiteKind::Castle) } } + +#[derive(PartialEq, Debug, Clone)] +pub struct PointOfInterest { + pub name: String, + pub kind: PoiKind, + pub loc: Vec2, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum PoiKind { + /// Peak stores the altitude + Peak(u32), + /// Lake stores a metric relating to size + Lake(u32), +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 6e515d1ef3..17ad70b013 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -112,6 +112,16 @@ impl World { let num_sites = self.civs().sites().count() as u64; let num_caves = self.civs().caves.values().count() as u64; WorldMapMsg { + pois: self.civs().pois.iter().map(|(_, poi)| { + world_msg::PoiInfo { + name: poi.name.clone(), + kind: match &poi.kind { + civ::PoiKind::Peak(alt) => world_msg::PoiKind::Peak(*alt), + civ::PoiKind::Lake(size) => world_msg::PoiKind::Lake(*size), + }, + wpos: poi.loc * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + } + }).collect(), sites: self .civs() .sites diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 97955d1cd7..cf6c16b6ff 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1514,6 +1514,7 @@ impl WorldSim { alt: Grid::from_raw(self.get_size().map(|e| e as i32), alts), horizons, sites: Vec::new(), // Will be substituted later + pois: Vec::new(), // Will be substituted later } }