Merge branch 'name_areas_based_on_biome' into 'master'

name_areas_based_on_biome

See merge request veloren/veloren!3115
This commit is contained in:
Joshua Barretto 2022-01-20 19:50:57 +00:00
commit 4f181c936d
14 changed files with 399 additions and 143 deletions

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

Binary file not shown.

View File

@ -14,6 +14,7 @@
"hud.map.caves": "Caves",
"hud.map.cave": "Cave",
"hud.map.peaks": "Mountains",
"hud.map.biomes": "Biomes",
"hud.map.voxel_map": "Voxel map",
"hud.map.trees": "Giant Trees",
"hud.map.tree": "Giant Tree",

View File

@ -131,7 +131,7 @@ impl TerrainChunkMeta {
}
}
pub fn name(&self) -> &str { self.name.as_deref().unwrap_or("Wilderness") }
pub fn name(&self) -> Option<&str> { self.name.as_deref() }
pub fn biome(&self) -> BiomeKind { self.biome }

View File

@ -414,6 +414,7 @@ image_ids! {
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_biome: "voxygen.element.ui.map.buttons.biome",
mmap_poi_peak_hover: "voxygen.element.ui.map.buttons.peak_hover",
// Window Parts

View File

@ -65,6 +65,9 @@ widget_ids! {
show_peaks_img,
show_peaks_box,
show_peaks_text,
show_biomes_img,
show_biomes_box,
show_biomes_text,
show_voxel_map_img,
show_voxel_map_box,
show_voxel_map_text,
@ -209,6 +212,7 @@ impl<'a> Widget for Map<'a> {
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_biomes = self.global_state.settings.interface.map_show_biomes;
let show_voxel_map = self.global_state.settings.interface.map_show_voxel_map;
let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
let mut events = Vec::new();
@ -641,9 +645,43 @@ impl<'a> Widget for Map<'a> {
.graphics_for(state.ids.show_trees_box)
.color(TEXT_COLOR)
.set(state.ids.show_trees_text, ui);
// Biomes
Image::new(self.imgs.mmap_poi_biome)
.down_from(state.ids.show_trees_img, 10.0)
.w_h(20.0, 20.0)
.set(state.ids.show_biomes_img, ui);
if Button::image(if show_biomes {
self.imgs.checkbox_checked
} else {
self.imgs.checkbox
})
.w_h(18.0, 18.0)
.hover_image(if show_biomes {
self.imgs.checkbox_checked_mo
} else {
self.imgs.checkbox_mo
})
.press_image(if show_biomes {
self.imgs.checkbox_checked
} else {
self.imgs.checkbox_press
})
.right_from(state.ids.show_biomes_img, 10.0)
.set(state.ids.show_biomes_box, ui)
.was_clicked()
{
events.push(Event::SettingsChange(MapShowBiomes(!show_biomes)));
}
Text::new(i18n.get("hud.map.biomes"))
.right_from(state.ids.show_biomes_box, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.show_biomes_box)
.color(TEXT_COLOR)
.set(state.ids.show_biomes_text, ui);
// Peaks
Image::new(self.imgs.mmap_poi_peak)
.down_from(state.ids.show_trees_img, 10.0)
.down_from(state.ids.show_biomes_img, 10.0)
.w_h(20.0, 20.0)
.set(state.ids.show_peaks_img, ui);
if Button::image(if show_peaks {
@ -1023,7 +1061,7 @@ impl<'a> Widget for Map<'a> {
}
},
PoiKind::Lake(size) => {
if zoom.powi(2) * size as f64 > 30.0 {
if show_biomes && zoom > 2.0 && zoom.powi(2) * size as f64 > 30.0 {
let font_scale_factor = if size > 20 {
size as f64 / 25.0
} else if size > 10 {

View File

@ -886,20 +886,22 @@ impl<'a> Widget for MiniMap<'a> {
match self.client.current_chunk() {
Some(chunk) => {
// Count characters in the name to avoid clipping with the name display
let name_len = chunk.meta().name().chars().count();
Text::new(chunk.meta().name())
.mid_top_with_margin_on(state.ids.mmap_frame, match name_len {
15..=30 => 4.0,
_ => 2.0,
})
.font_size(self.fonts.cyri.scale(match name_len {
0..=15 => 18,
16..=30 => 14,
_ => 14,
}))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.mmap_location, ui)
if let Some(name) = chunk.meta().name() {
let name_len = name.chars().count();
Text::new(name)
.mid_top_with_margin_on(state.ids.mmap_frame, match name_len {
15..=30 => 4.0,
_ => 2.0,
})
.font_size(self.fonts.cyri.scale(match name_len {
0..=15 => 18,
16..=30 => 14,
_ => 14,
}))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.mmap_location, ui)
}
},
None => Text::new(" ")
.mid_top_with_margin_on(state.ids.mmap_frame, 0.0)

View File

@ -98,23 +98,24 @@ impl<'a> Widget for Popup<'a> {
// Push chunk name to message queue
if let Some(chunk) = self.client.current_chunk() {
let current = chunk.meta().name();
// Check if no other popup is displayed and a new one is needed
if state.messages.is_empty()
&& state
.last_region_name
.as_ref()
.map(|l| l != current)
.unwrap_or(true)
{
// Update last_region
state.update(|s| {
if s.messages.is_empty() {
s.last_message_update = Instant::now();
}
s.last_region_name = Some(current.to_owned());
s.messages.push_back(current.to_owned());
});
if let Some(current) = chunk.meta().name() {
// Check if no other popup is displayed and a new one is needed
if state.messages.is_empty()
&& state
.last_region_name
.as_ref()
.map(|l| l != current)
.unwrap_or(true)
{
// Update last_region
state.update(|s| {
if s.messages.is_empty() {
s.last_message_update = Instant::now();
}
s.last_region_name = Some(current.to_owned());
s.messages.push_back(current.to_owned());
});
}
}
}

View File

@ -129,6 +129,7 @@ pub enum Interface {
MapShowCaves(bool),
MapShowTrees(bool),
MapShowPeaks(bool),
MapShowBiomes(bool),
MapShowVoxelMap(bool),
ResetInterfaceSettings,
@ -535,6 +536,9 @@ impl SettingsChange {
Interface::MapShowPeaks(map_show_peaks) => {
settings.interface.map_show_peaks = map_show_peaks;
},
Interface::MapShowBiomes(map_show_biomes) => {
settings.interface.map_show_biomes = map_show_biomes;
},
Interface::MapShowVoxelMap(map_show_voxel_map) => {
settings.interface.map_show_voxel_map = map_show_voxel_map;
},

View File

@ -38,6 +38,7 @@ pub struct InterfaceSettings {
pub map_show_caves: bool,
pub map_show_trees: bool,
pub map_show_peaks: bool,
pub map_show_biomes: bool,
pub map_show_voxel_map: bool,
pub minimap_show: bool,
pub minimap_face_north: bool,
@ -77,6 +78,7 @@ impl Default for InterfaceSettings {
map_show_caves: true,
map_show_trees: false,
map_show_peaks: false,
map_show_biomes: false,
map_show_voxel_map: true,
minimap_show: true,
minimap_face_north: true,

View File

@ -112,6 +112,7 @@ impl<'a> CanvasInfo<'a> {
surface_veg: 0.0,
sites: Vec::new(),
place: None,
poi: None,
path: Default::default(),
cave: Default::default(),
cliff_height: 0.0,

View File

@ -4,10 +4,10 @@ mod econ;
use crate::{
config::CONFIG,
sim::{RiverKind, WorldSim},
sim::WorldSim,
site::{namegen::NameGen, Castle, Settlement, Site as WorldSite, Tree},
site2,
util::{attempt, seed_expan, CARDINALS, NEIGHBORS},
util::{attempt, seed_expan, NEIGHBORS},
Index, Land,
};
use common::{
@ -15,7 +15,7 @@ use common::{
path::Path,
spiral::Spiral2d,
store::{Id, Store},
terrain::{uniform_idx_as_vec2, vec2_as_uniform_idx, MapSizeLg, TerrainChunkSize},
terrain::{uniform_idx_as_vec2, MapSizeLg, TerrainChunkSize},
vol::RectVolSize,
};
use core::{fmt, hash::BuildHasherDefault, ops::Range};
@ -89,8 +89,8 @@ impl Civs {
let mut ctx = GenCtx { sim, rng };
info!("starting peak naming");
this.name_peaks(&mut ctx);
info!("starting lake naming");
this.name_lakes(&mut ctx);
info!("starting biome naming");
this.name_biomes(&mut ctx);
for _ in 0..ctx.sim.get_size().product() / 10_000 {
this.generate_cave(&mut ctx);
@ -468,113 +468,220 @@ impl Civs {
}
/// Adds lake POIs and names them
fn name_lakes(&mut self, ctx: &mut GenCtx<impl Rng>) {
fn name_biomes(&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 biomes: Vec<(common::terrain::BiomeKind, Vec<usize>)> = Vec::new();
let mut explored = HashSet::new();
let mut to_explore = HashSet::new();
let mut to_floodfill = HashSet::new();
// TODO: have start point in center and ignore ocean?
let start_point = 0;
to_explore.insert(start_point);
explored.insert(start_point);
while !to_explore.is_empty() {
let exploring = *to_explore.iter().next().unwrap();
to_explore.remove(&exploring);
to_floodfill.insert(exploring);
// Should always be a chunk on the map
let biome = ctx.sim.chunks[exploring].get_biome();
biomes.push((biome, Vec::new()));
while !to_floodfill.is_empty() {
let filling = *to_floodfill.iter().next().unwrap();
to_explore.remove(&filling);
to_floodfill.remove(&filling);
explored.insert(filling);
biomes.last_mut().unwrap().1.push(filling);
for neighbour in common::terrain::neighbors(map_size_lg, filling) {
if explored.contains(&neighbour) {
continue;
}
let n_biome = ctx.sim.chunks[neighbour].get_biome();
if n_biome == biome {
to_floodfill.insert(neighbour);
} else {
to_explore.insert(neighbour);
}
}
}
}
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),
}
}
let mut biome_count = 0;
for biome in biomes {
let name = match biome.0 {
common::terrain::BiomeKind::Forest if biome.1.len() as u32 > 750 => Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_temp_forest(),
[
"Forest",
"Woodlands",
"Woods",
"Glades",
"Grove",
"Thickets",
"Weald"
]
.choose(&mut ctx.rng)
.unwrap()
)),
common::terrain::BiomeKind::Grassland if biome.1.len() as u32 > 750 => {
Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_grassland(),
[
"Grasslands",
"Flats",
"Greens",
"Plains",
"Meadows",
"Fields",
"Heath",
"Steppe",
"Downs"
]
.choose(&mut ctx.rng)
.unwrap()
))
},
// 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,
common::terrain::BiomeKind::Ocean if biome.1.len() as u32 > 750 => Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
["Ocean", "Blue", "Deep"].choose(&mut ctx.rng).unwrap()
)),
common::terrain::BiomeKind::Mountain if biome.1.len() as u32 > 750 => {
Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
[
"Mountains",
"Range",
"Reach",
"Massif",
"Rocks",
"Cliffs",
"Peaks",
"Heights",
"Bluffs",
"Ridge",
"Canyon",
"Plateau"
]
.choose(&mut ctx.rng)
.unwrap()
))
},
common::terrain::BiomeKind::Snowland if biome.1.len() as u32 > 750 => {
Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
[
"Snowlands",
"Glacier",
"Tundra",
"Snowfields",
"Hills",
"Highlands"
]
.choose(&mut ctx.rng)
.unwrap()
))
},
common::terrain::BiomeKind::Desert if biome.1.len() as u32 > 750 => Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
["Desert", "Sands", "Sandsea", "Drifts", "Dunes", "Sandfield"]
.choose(&mut ctx.rng)
.unwrap()
)),
common::terrain::BiomeKind::Mountain if biome.1.len() as u32 > 750 => {
Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
[
"Swamp",
"Swamps",
"Swamplands",
"Marsh",
"Marshlands",
"Morass",
"Mire",
"Bog",
"Snowlands"
]
.choose(&mut ctx.rng)
.unwrap()
))
},
common::terrain::BiomeKind::Jungle if biome.1.len() as u32 > 750 => Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
[
"Jungle",
"Rainforest",
"Greatwood",
"Wilds",
"Wildwood",
"Tangle",
"Tanglewood",
"Bush"
]
.choose(&mut ctx.rng)
.unwrap()
)),
common::terrain::BiomeKind::Savannah if biome.1.len() as u32 > 750 => {
Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
["Savannah", "Shrubland", "Sierra", "Prairie", "Lowlands"]
.choose(&mut ctx.rng)
.unwrap()
))
},
common::terrain::BiomeKind::Taiga if biome.1.len() as u32 > 750 => Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate_biome(),
["Taiga", "Timberlands", "Uplands", "Highlands"]
.choose(&mut ctx.rng)
.unwrap()
)),
common::terrain::BiomeKind::Lake if biome.1.len() as u32 > 200 => Some(format!(
"{} {}",
["Lake", "Loch"].choose(&mut ctx.rng).unwrap(),
NameGen::location(&mut ctx.rng).generate()
)),
common::terrain::BiomeKind::Lake if biome.1.len() as u32 > 10 => Some(format!(
"{} {}",
NameGen::location(&mut ctx.rng).generate(),
["Pool", "Well", "Pond"].choose(&mut ctx.rng).unwrap()
)),
_ => None,
};
num_lakes += 1;
self.pois.insert(lake);
if let Some(name) = name {
// find average center of the biome
let center = biome
.1
.iter()
.map(|b| uniform_idx_as_vec2(map_size_lg, *b))
.sum::<Vec2<i32>>()
/ biome.1.len() as i32;
// Select the point closest to the center
let idx = *biome
.1
.iter()
.min_by_key(|&b| center.distance_squared(uniform_idx_as_vec2(map_size_lg, *b)))
.unwrap();
let id = self.pois.insert(PointOfInterest {
name,
loc: uniform_idx_as_vec2(map_size_lg, idx),
kind: PoiKind::Biome(biome.1.len() as u32),
});
for chunk in biome.1 {
ctx.sim.chunks[chunk].poi = Some(id);
}
biome_count += 1;
}
}
info!(?num_lakes, "all lakes named");
info!(?biome_count, "all biomes named");
}
/// Adds mountain POIs and name them
@ -934,5 +1041,5 @@ pub enum PoiKind {
/// Peak stores the altitude
Peak(u32),
/// Lake stores a metric relating to size
Lake(u32),
Biome(u32),
}

View File

@ -131,7 +131,7 @@ impl World {
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),
civ::PoiKind::Biome(size) => world_msg::PoiKind::Lake(*size),
},
wpos: poi.loc * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
}
@ -282,7 +282,8 @@ impl World {
.get_origin()
.distance_squared(chunk_center_wpos2d)
})
.map(|id| index.sites[*id].name().to_string()),
.map(|id| index.sites[*id].name().to_string())
.or_else(|| sim_chunk.poi.map(|poi| self.civs.pois[poi].name.clone())),
sim_chunk.get_biome(),
sim_chunk.alt,
sim_chunk.tree_density,

View File

@ -28,7 +28,7 @@ pub(crate) use self::{
use crate::{
all::{Environment, ForestKind, TreeAttr},
block::BlockGen,
civ::Place,
civ::{Place, PointOfInterest},
column::ColumnGen,
layer::spot::Spot,
site::Site,
@ -2178,6 +2178,7 @@ pub struct SimChunk {
pub sites: Vec<Id<Site>>,
pub place: Option<Id<Place>>,
pub poi: Option<Id<PointOfInterest>>,
pub path: (Way, Path),
pub cave: (Way, Cave),
@ -2419,6 +2420,7 @@ impl SimChunk {
sites: Vec::new(),
place: None,
poi: None,
path: Default::default(),
cave: Default::default(),
cliff_height: 0.0,

View File

@ -3,6 +3,7 @@ use rand::prelude::*;
pub struct NameGen<'a, R: Rng> {
// 2..
pub approx_syllables: usize,
pub approx_syllables_long: usize,
rng: &'a mut R,
}
@ -11,6 +12,8 @@ impl<'a, R: Rng> NameGen<'a, R> {
pub fn location(rng: &'a mut R) -> Self {
Self {
approx_syllables: rng.gen_range(1..4),
approx_syllables_long: rng.gen_range(2..4),
rng,
}
}
@ -49,4 +52,94 @@ impl<'a, R: Rng> NameGen<'a, R> {
.map(|(i, c)| if i == 0 { c.to_ascii_uppercase() } else { c })
.collect()
}
pub fn generate_biome(self) -> String {
let cons = vec![
"b", "d", "f", "g", "h", "k", "l", "m", "n", "s", "t", "w", "br", "dr", "gr", "gh",
"kh", "kr", "st", "str", "th", "tr", "ar", "ark", "adr", "ath", "an", "el", "elb",
"eldr", "estr", "ostr", "ond", "ondr", "ul", "uld", "eld", "eldr",
];
let start = cons.clone();
let mid = vec![
"br", "d", "dr", "dn", "dm", "fr", "g", "gr", "gl", "k", "kr", "l", "ll", "m", "mm",
"n", "nn", "nd", "st", "th", "rw", "nw", "thr", "lk", "nk", "ng", "rd", "rk", "nr",
"nth", "rth", "kn", "rl", "gg", "lg", "str", "nb", "lb", "ld", "rm", "sd", "sb",
];
let mut middle = mid.clone();
middle.extend(vec!["tt"]);
let vowel = vec!["o", "e", "a", "u", "ae"];
let end = vec![
"ul", "um", "un", "uth", "und", "ur", "an", "a", "ar", "a", "amar", "amur", "ath",
"or", "on", "oth", "omor", "omur", "omar", "ador", "odor", "en", "end", "eth", "amon",
"edur", "aden", "oden", "alas", "elas", "alath", "aloth", "eloth", "eres", "ond",
"ondor", "undor", "andor", "od", "ed", "amad", "ud", "amud", "ulud", "alud", "allen",
"alad", "and", "an", "as", "es",
];
let mut name = String::new();
name += start.choose(self.rng).unwrap();
for _ in 0..self.approx_syllables_long.saturating_sub(2) {
name += vowel.choose(self.rng).unwrap();
name += middle.choose(self.rng).unwrap();
}
name += end.choose(self.rng).unwrap();
name.chars()
.enumerate()
.map(|(i, c)| if i == 0 { c.to_ascii_uppercase() } else { c })
.collect()
}
pub fn generate_temp_forest(self) -> String {
let cons = vec![
"green", "moss", "ever", "briar", "thorn", "oak", "deep", "moon", "star", "sun",
"bright", "glare", "fair", "calm", "mistral", "whisper", "clover", "hollow", "spring",
"morrow", "dim", "dusk", "dawn", "night", "shimmer", "silver", "gold", "whisper",
"fern", "quiet", "still", "gleam", "wild", "blind", "swift", "gnarl", "flutter",
"silent", "honey", "bramble", "rose",
];
let start = cons.clone();
let end = vec![
"root", "bark", "log", "brook", "well", "shire", "leaf", "more", "bole", "heart",
"song", "dew", "bough", "path", "wind", "breeze", "light", "branch", "bloom", "vale",
"glen", "rest", "shade", "fall", "sward", "thicket", "shrub", "bush", "grasp", "grip",
"gale", "crawl", "run", "shadow", "rise", "glow", "wish", "will", "walk", "wander",
"wake", "eye", "blossom", "sprout", "barb",
];
let mut name = String::new();
name += start.choose(self.rng).unwrap();
name += end.choose(self.rng).unwrap();
name.chars()
.enumerate()
.map(|(i, c)| if i == 0 { c.to_ascii_uppercase() } else { c })
.collect()
}
pub fn generate_grassland(self) -> String {
let cons = vec![
"green", "heather", "flower", "blue", "yellow", "vast", "moon", "star", "sun",
"bright", "fair", "calm", "mistral", "whisper", "clover", "sooth", "spring", "morrow",
"dim", "dusk", "dawn", "night", "shimmer", "silver", "gold", "amber", "quiet", "still",
"gleam", "wild", "corm", "mint", "feather", "silent", "bronze", "bister", "thistle",
"bristle", "dew", "bramble", "sorrel", "broad", "petal",
];
let start = cons.clone();
let end = vec![
"brook", "well", "flight", "more", "heart", "song", "barb", "wort", "hoof", "foot",
"herd", "path", "wind", "breeze", "light", "bloom", "rest", "balm", "reach", "flow",
"graze", "trail", "fall", "thicket", "shrub", "bush", "gale", "run", "stem", "glare",
"gaze", "rove", "brew", "rise", "glow", "wish", "will", "walk", "wander", "wake",
"sky", "burrow", "cross", "roam",
];
let mut name = String::new();
name += start.choose(self.rng).unwrap();
name += end.choose(self.rng).unwrap();
name.chars()
.enumerate()
.map(|(i, c)| if i == 0 { c.to_ascii_uppercase() } else { c })
.collect()
}
}