Add map markers for lakes and mountains

This commit is contained in:
James Melkonian 2021-05-03 02:00:23 +00:00 committed by Justin Shipsey
parent 435d515945
commit f553700e8c
15 changed files with 419 additions and 14 deletions

View File

@ -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. - Specific music tracks can now play exclusively in towns.
- Custom map markers can be placed now - Custom map markers can be placed now
- Fundamentals/prototype for wiring system - Fundamentals/prototype for wiring system
- Mountain peak and lake markers on the map
### Changed ### 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. - Skillbar buttons correctly account for skill points when checking if player has enough stamina for the ability.
- Burning Debuff icon is now displayed correctly. - Burning Debuff icon is now displayed correctly.
- Villagers in safezones no longer spam messages upon seeing an enemy - 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 ## [0.9.0] - 2021-03-20

BIN
assets/voxygen/element/ui/map/buttons/peak.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/ui/map/buttons/peak_hover.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -13,6 +13,7 @@
"hud.map.dungeons": "Dungeons", "hud.map.dungeons": "Dungeons",
"hud.map.caves": "Caves", "hud.map.caves": "Caves",
"hud.map.cave": "Cave", "hud.map.cave": "Cave",
"hud.map.peaks": "Mountains",
"hud.map.trees": "Giant Trees", "hud.map.trees": "Giant Trees",
"hud.map.tree": "Giant Tree", "hud.map.tree": "Giant Tree",
"hud.map.town": "Town", "hud.map.town": "Town",

View File

@ -47,7 +47,7 @@ use common_base::span;
use common_net::{ use common_net::{
msg::{ msg::{
self, validate_chat_msg, self, validate_chat_msg,
world_msg::{EconomyInfo, SiteId, SiteInfo}, world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
ChatMsgValidationError, ClientGeneral, ClientMsg, ClientRegister, ClientType, ChatMsgValidationError, ClientGeneral, ClientMsg, ClientRegister, ClientType,
DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate,
PresenceKind, RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer, PresenceKind, RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer,
@ -152,6 +152,7 @@ pub struct Client {
player_list: HashMap<Uid, PlayerInfo>, player_list: HashMap<Uid, PlayerInfo>,
character_list: CharacterList, character_list: CharacterList,
sites: HashMap<SiteId, SiteInfoRich>, sites: HashMap<SiteId, SiteInfoRich>,
pois: Vec<PoiInfo>,
pub chat_mode: ChatMode, pub chat_mode: ChatMode,
recipe_book: RecipeBook, recipe_book: RecipeBook,
available_recipes: HashMap<String, Option<SpriteKind>>, available_recipes: HashMap<String, Option<SpriteKind>>,
@ -266,6 +267,7 @@ impl Client {
lod_horizon, lod_horizon,
world_map, world_map,
sites, sites,
pois,
recipe_book, recipe_book,
max_group_size, max_group_size,
client_timeout, client_timeout,
@ -628,6 +630,7 @@ impl Client {
Grid::from_raw(map_size.map(|e| e as i32), lod_horizon), Grid::from_raw(map_size.map(|e| e as i32), lod_horizon),
(world_map_layers, map_size, map_bounds), (world_map_layers, map_size, map_bounds),
world_map.sites, world_map.sites,
world_map.pois,
recipe_book, recipe_book,
max_group_size, max_group_size,
client_timeout, client_timeout,
@ -661,6 +664,7 @@ impl Client {
}) })
}) })
.collect(), .collect(),
pois,
recipe_book, recipe_book,
available_recipes: HashMap::default(), available_recipes: HashMap::default(),
chat_mode: ChatMode::default(), chat_mode: ChatMode::default(),
@ -1036,6 +1040,9 @@ impl Client {
/// Unstable, likely to be removed in a future release /// Unstable, likely to be removed in a future release
pub fn sites(&self) -> &HashMap<SiteId, SiteInfoRich> { &self.sites } pub fn sites(&self) -> &HashMap<SiteId, SiteInfoRich> { &self.sites }
/// Unstable, likely to be removed in a future release
pub fn pois(&self) -> &Vec<PoiInfo> { &self.pois }
pub fn sites_mut(&mut self) -> &mut HashMap<SiteId, SiteInfoRich> { &mut self.sites } pub fn sites_mut(&mut self) -> &mut HashMap<SiteId, SiteInfoRich> { &mut self.sites }
pub fn enable_lantern(&mut self) { pub fn enable_lantern(&mut self) {

View File

@ -123,6 +123,7 @@ pub struct WorldMapMsg {
/// (256 possible angles). /// (256 possible angles).
pub horizons: [(Vec<u8>, Vec<u8>); 2], pub horizons: [(Vec<u8>, Vec<u8>); 2],
pub sites: Vec<SiteInfo>, pub sites: Vec<SiteInfo>,
pub pois: Vec<PoiInfo>,
} }
pub type SiteId = common::trade::SiteId; pub type SiteId = common::trade::SiteId;
@ -156,3 +157,17 @@ pub struct EconomyInfo {
pub last_exports: HashMap<Good, f32>, pub last_exports: HashMap<Good, f32>,
pub resources: HashMap<Good, f32>, pub resources: HashMap<Good, f32>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoiInfo {
pub kind: PoiKind,
pub wpos: Vec2<i32>,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[repr(u8)]
pub enum PoiKind {
Peak(u32),
Lake(u32),
}

View File

@ -190,7 +190,6 @@ pub struct Diary<'a> {
created_btns_top_r: usize, created_btns_top_r: usize,
created_btns_bot_l: usize, created_btns_bot_l: usize,
created_btns_bot_r: usize, created_btns_bot_r: usize,
hovering_exp_bar: bool,
} }
impl<'a> Diary<'a> { impl<'a> Diary<'a> {
@ -222,7 +221,6 @@ impl<'a> Diary<'a> {
created_btns_top_r: 0, created_btns_top_r: 0,
created_btns_bot_l: 0, created_btns_bot_l: 0,
created_btns_bot_r: 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) .middle_of(state.exp_bar_bg)
.set(state.exp_bar_frame, ui); .set(state.exp_bar_frame, ui);
// Show EXP bar text on hover // Show EXP bar text on hover
self.hovering_exp_bar = ui if ui
.widget_input(state.exp_bar_frame) .widget_input(state.exp_bar_frame)
.mouse() .mouse()
.map_or(false, |m| m.is_over()); .map_or(false, |m| m.is_over())
if self.hovering_exp_bar { {
Text::new(&exp_txt) Text::new(&exp_txt)
.mid_top_with_margin_on(state.exp_bar_frame, 47.0) .mid_top_with_margin_on(state.exp_bar_frame, 47.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)

View File

@ -382,6 +382,8 @@ image_ids! {
mmap_site_excl: "voxygen.element.ui.map.buttons.excl", mmap_site_excl: "voxygen.element.ui.map.buttons.excl",
mmap_site_tree: "voxygen.element.ui.map.buttons.tree", mmap_site_tree: "voxygen.element.ui.map.buttons.tree",
mmap_site_tree_hover: "voxygen.element.ui.map.buttons.tree_hover", 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 Parts
window_3: "voxygen.element.ui.generic.frames.window_3", window_3: "voxygen.element.ui.generic.frames.window_3",

View File

@ -1,7 +1,7 @@
use super::{ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
Show, QUALITY_COMMON, QUALITY_DEBUG, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE, 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::{ use crate::{
i18n::Localization, i18n::Localization,
@ -11,7 +11,7 @@ use crate::{
}; };
use client::{self, Client, SiteInfoRich}; use client::{self, Client, SiteInfoRich};
use common::{comp, comp::group::Role, terrain::TerrainChunkSize, trade::Good, vol::RectVolSize}; 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::{ use conrod_core::{
color, position, color, position,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
@ -37,6 +37,11 @@ widget_ids! {
qlog_title, qlog_title,
zoom_slider, zoom_slider,
mmap_site_icons[], mmap_site_icons[],
mmap_poi_icons[],
mmap_poi_title_bgs[],
mmap_poi_titles[],
peaks_txt,
peaks_txt_bg,
site_difs[], site_difs[],
member_indicators[], member_indicators[],
member_height_indicators[], member_height_indicators[],
@ -57,6 +62,9 @@ widget_ids! {
show_trees_img, show_trees_img,
show_trees_box, show_trees_box,
show_trees_text, show_trees_text,
show_peaks_img,
show_peaks_box,
show_peaks_text,
show_difficulty_img, show_difficulty_img,
show_difficulty_box, show_difficulty_box,
show_difficulty_text, 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_castles = self.global_state.settings.interface.map_show_castles;
let show_caves = self.global_state.settings.interface.map_show_caves; let show_caves = self.global_state.settings.interface.map_show_caves;
let show_trees = self.global_state.settings.interface.map_show_trees; 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 show_topo_map = self.global_state.settings.interface.map_show_topo_map;
let mut events = Vec::new(); let mut events = Vec::new();
let i18n = &self.localized_strings; let i18n = &self.localized_strings;
@ -371,6 +380,9 @@ impl<'a> Widget for Map<'a> {
} }
// Handle zooming with the mousewheel // Handle zooming with the mousewheel
// TODO: Experiment with zooming around cursor position instead of map center
// (issue #1111)
let scrolled: f64 = ui let scrolled: f64 = ui
.widget_input(state.ids.map_layers[0]) .widget_input(state.ids.map_layers[0])
.scrolls() .scrolls()
@ -590,7 +602,61 @@ impl<'a> Widget for Map<'a> {
.graphics_for(state.ids.show_trees_box) .graphics_for(state.ids.show_trees_box)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.show_trees_text, ui); .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 // 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() { if state.ids.mmap_site_icons.len() < self.client.sites().len() {
state.update(|state| { state.update(|state| {
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 // Group member indicators
let client_state = self.client.state(); let client_state = self.client.state();
let stats = client_state.ecs().read_storage::<common::comp::Stats>(); let stats = client_state.ecs().read_storage::<common::comp::Stats>();

View File

@ -105,6 +105,7 @@ use vek::*;
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); 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_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_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_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); const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);

View File

@ -111,6 +111,7 @@ pub enum Interface {
MapShowCastles(bool), MapShowCastles(bool),
MapShowCaves(bool), MapShowCaves(bool),
MapShowTrees(bool), MapShowTrees(bool),
MapShowPeaks(bool),
ResetInterfaceSettings, ResetInterfaceSettings,
} }
@ -454,6 +455,9 @@ impl SettingsChange {
Interface::MapShowTrees(map_show_trees) => { Interface::MapShowTrees(map_show_trees) => {
settings.interface.map_show_trees = 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 => { Interface::ResetInterfaceSettings => {
// Reset Interface Settings // Reset Interface Settings
let tmp = settings.interface.intro_show; let tmp = settings.interface.intro_show;

View File

@ -35,6 +35,7 @@ pub struct InterfaceSettings {
pub loading_tips: bool, pub loading_tips: bool,
pub map_show_caves: bool, pub map_show_caves: bool,
pub map_show_trees: bool, pub map_show_trees: bool,
pub map_show_peaks: bool,
pub minimap_show: bool, pub minimap_show: bool,
pub minimap_face_north: bool, pub minimap_face_north: bool,
pub minimap_zoom: f64, pub minimap_zoom: f64,
@ -65,10 +66,11 @@ impl Default for InterfaceSettings {
map_show_difficulty: true, map_show_difficulty: true,
map_show_towns: true, map_show_towns: true,
map_show_dungeons: true, map_show_dungeons: true,
map_show_castles: true, map_show_castles: false,
loading_tips: true, loading_tips: true,
map_show_caves: true, map_show_caves: true,
map_show_trees: true, map_show_trees: false,
map_show_peaks: false,
minimap_show: true, minimap_show: true,
minimap_face_north: false, minimap_face_north: false,
minimap_zoom: 10.0, minimap_zoom: 10.0,

View File

@ -4,10 +4,10 @@ mod econ;
use crate::{ use crate::{
config::CONFIG, config::CONFIG,
sim::WorldSim, sim::{RiverKind, WorldSim},
site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite, Tree}, site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite, Tree},
site2, site2,
util::{attempt, seed_expan, NEIGHBORS}, util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
Index, Land, Index, Land,
}; };
use common::{ use common::{
@ -15,12 +15,12 @@ use common::{
path::Path, path::Path,
spiral::Spiral2d, spiral::Spiral2d,
store::{Id, Store}, store::{Id, Store},
terrain::{uniform_idx_as_vec2, MapSizeLg, TerrainChunkSize}, terrain::{uniform_idx_as_vec2, vec2_as_uniform_idx, MapSizeLg, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
}; };
use core::{fmt, hash::BuildHasherDefault, ops::Range}; use core::{fmt, hash::BuildHasherDefault, ops::Range};
use fxhash::FxHasher64; use fxhash::FxHasher64;
use hashbrown::HashMap; use hashbrown::{HashMap, HashSet};
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaChaRng; use rand_chacha::ChaChaRng;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
@ -44,6 +44,7 @@ pub struct CaveInfo {
pub struct Civs { pub struct Civs {
pub civs: Store<Civ>, pub civs: Store<Civ>,
pub places: Store<Place>, pub places: Store<Place>,
pub pois: Store<PointOfInterest>,
pub tracks: Store<Track>, pub tracks: Store<Track>,
/// We use this hasher (FxHasher64) because /// We use this hasher (FxHasher64) because
@ -62,6 +63,7 @@ pub struct Civs {
// Change this to get rid of particularly horrid seeds // Change this to get rid of particularly horrid seeds
const SEED_SKIP: u8 = 5; const SEED_SKIP: u8 = 5;
const POI_THINNING_DIST_SQRD: i32 = 300;
pub struct GenCtx<'a, R: Rng> { pub struct GenCtx<'a, R: Rng> {
sim: &'a mut WorldSim, sim: &'a mut WorldSim,
@ -85,6 +87,10 @@ impl Civs {
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let initial_civ_count = initial_civ_count(sim.map_size_lg()); let initial_civ_count = initial_civ_count(sim.map_size_lg());
let mut ctx = GenCtx { sim, rng }; 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 { for _ in 0..ctx.sim.get_size().product() / 10_000 {
this.generate_cave(&mut ctx); this.generate_cave(&mut ctx);
@ -456,6 +462,191 @@ impl Civs {
self.places.insert(Place { center: loc }) self.places.insert(Place { center: loc })
} }
/// Adds lake POIs and names them
fn name_lakes(&mut self, ctx: &mut GenCtx<impl Rng>) {
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::<Vec<(Vec2<i32>, 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<impl Rng>) {
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::<Vec<(usize, Vec2<i32>, 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( fn establish_site(
&mut self, &mut self,
ctx: &mut GenCtx<impl Rng>, ctx: &mut GenCtx<impl Rng>,
@ -726,3 +917,18 @@ impl Site {
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)]
pub struct PointOfInterest {
pub name: String,
pub kind: PoiKind,
pub loc: Vec2<i32>,
}
#[derive(PartialEq, Debug, Clone)]
pub enum PoiKind {
/// Peak stores the altitude
Peak(u32),
/// Lake stores a metric relating to size
Lake(u32),
}

View File

@ -112,6 +112,16 @@ impl World {
let num_sites = self.civs().sites().count() as u64; let num_sites = self.civs().sites().count() as u64;
let num_caves = self.civs().caves.values().count() as u64; let num_caves = self.civs().caves.values().count() as u64;
WorldMapMsg { 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 sites: self
.civs() .civs()
.sites .sites

View File

@ -1514,6 +1514,7 @@ impl WorldSim {
alt: Grid::from_raw(self.get_size().map(|e| e as i32), alts), alt: Grid::from_raw(self.get_size().map(|e| e as i32), alts),
horizons, horizons,
sites: Vec::new(), // Will be substituted later sites: Vec::new(), // Will be substituted later
pois: Vec::new(), // Will be substituted later
} }
} }