Added site names, dungeon difficulty, better explosions

This commit is contained in:
Joshua Barretto 2020-11-19 00:23:13 +00:00
parent 5b907ede9e
commit 7850b0bcaf
19 changed files with 240 additions and 71 deletions

View File

@ -105,13 +105,16 @@ impl ProjectileConstructor {
} => Projectile {
hit_solid: vec![
Effect::Explode(Explosion {
effects: vec![RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Explosion,
value: damage,
}),
)],
effects: vec![
RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Explosion,
value: damage,
}),
),
RadiusEffect::TerrainDestruction(2.0),
],
radius,
energy_regen,
}),

View File

@ -133,6 +133,11 @@ impl Stats {
body_type: comp::Body::Humanoid(comp::body::humanoid::Body::random()),
}
}
pub fn with_level(mut self, level: u32) -> Self {
self.level.set_level(level);
self
}
}
impl Component for Stats {

View File

@ -134,6 +134,6 @@ pub struct SiteInfo {
#[repr(u8)]
pub enum SiteKind {
Town,
Dungeon,
Dungeon { difficulty: u32 },
Castle,
}

View File

@ -1,10 +1,9 @@
use crate::{span, vol::ReadVol};
use vek::*;
pub trait RayUntil<V> = FnMut(&V) -> bool;
pub trait RayForEach<V> = FnMut(&V, Vec3<i32>);
pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> {
pub struct Ray<'a, V: ReadVol, F: FnMut(&V::Vox) -> bool, G: RayForEach<V::Vox>> {
vol: &'a V,
from: Vec3<f32>,
to: Vec3<f32>,
@ -14,7 +13,7 @@ pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> {
ignore_error: bool,
}
impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> Ray<'a, V, F, G> {
impl<'a, V: ReadVol, F: FnMut(&V::Vox) -> bool, G: RayForEach<V::Vox>> Ray<'a, V, F, G> {
pub fn new(vol: &'a V, from: Vec3<f32>, to: Vec3<f32>, until: F) -> Self {
Self {
vol,
@ -27,7 +26,17 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> Ray<'a, V, F, G
}
}
pub fn until(self, f: F) -> Ray<'a, V, F, G> { Ray { until: f, ..self } }
pub fn until<H: FnMut(&V::Vox) -> bool>(self, f: H) -> Ray<'a, V, H, G> {
Ray {
vol: self.vol,
from: self.from,
to: self.to,
until: f,
for_each: self.for_each,
max_iter: self.max_iter,
ignore_error: self.ignore_error,
}
}
pub fn for_each<H: RayForEach<V::Vox>>(self, f: H) -> Ray<'a, V, F, H> {
Ray {

View File

@ -711,7 +711,7 @@ impl<'a> System<'a> for Sys {
},
Collider::Point => {
let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta)
.until(|block| block.is_filled())
.until(|block: &Block| block.is_filled())
.ignore_error().cast();
pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist;

View File

@ -184,13 +184,16 @@ impl Block {
}
#[inline]
pub fn is_explodable(&self) -> bool {
pub fn explode_power(&self) -> Option<f32> {
match self.kind() {
BlockKind::Leaves | BlockKind::Grass | BlockKind::WeakRock => true,
BlockKind::Leaves => Some(0.25),
BlockKind::Grass => Some(0.5),
BlockKind::WeakRock => Some(0.75),
BlockKind::Snow => Some(0.1),
// Explodable means that the terrain sprite will get removed anyway, so all is good for
// empty fluids.
// TODO: Handle the case of terrain sprites we don't want to have explode
_ => self.get_sprite().is_some(),
_ => self.get_sprite().map(|_| 0.25),
}
}

View File

@ -620,9 +620,10 @@ pub fn handle_explosion(
+ (fade * (color[1] as f32 * 0.3 - color[1] as f32));
let b = color[2] as f32
+ (fade * (color[2] as f32 * 0.3 - color[2] as f32));
color[0] = r as u8;
color[1] = g as u8;
color[2] = b as u8;
// Darken blocks, but not too much
color[0] = (r as u8).max(30);
color[1] = (g as u8).max(30);
color[2] = (b as u8).max(30);
block_change.set(block_pos, Block::new(block.kind(), color));
}
}
@ -637,13 +638,19 @@ pub fn handle_explosion(
)
.normalized();
let mut ray_energy = power;
let terrain = ecs.read_resource::<TerrainGrid>();
let _ = terrain
.ray(pos, pos + dir * power)
// TODO: Faster RNG
.until(|block| block.is_liquid() || rand::random::<f32>() < 0.05)
.until(|block: &Block| {
let stop = block.is_liquid() || block.explode_power().is_none() || ray_energy <= 0.0;
ray_energy -= block.explode_power().unwrap_or(0.0) + rand::random::<f32>() * 0.1;
stop
})
.for_each(|block: &Block, pos| {
if block.is_explodable() {
if block.explode_power().is_some() {
block_change.set(pos, block.into_vacant());
}
})

View File

@ -24,6 +24,7 @@ pub struct Entity {
const PERM_SPECIES: u32 = 0;
const PERM_BODY: u32 = 1;
const PERM_LOADOUT: u32 = 2;
const PERM_LEVEL: u32 = 3;
impl Entity {
pub fn rng(&self, perm: u32) -> impl Rng {
@ -35,6 +36,10 @@ impl Entity {
comp::humanoid::Body::random_with(&mut self.rng(PERM_BODY), &species).into()
}
pub fn get_level(&self) -> u32 {
(self.rng(PERM_LEVEL).gen::<f32>().powf(2.0) * 15.0).ceil() as u32
}
pub fn get_loadout(&self, ability_map: &AbilityMap) -> comp::Loadout {
let mut rng = self.rng(PERM_LOADOUT);
let main_tool = comp::Item::new_from_asset_expect((&[
@ -106,7 +111,7 @@ impl Entity {
let travel_to_alt = world.sim().get_alt_approx(travel_to.map(|e| e as i32)).unwrap_or(0.0) as i32;
let travel_to = terrain.find_space(Vec3::new(travel_to.x as i32, travel_to.y as i32, travel_to_alt)).map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
self.controller.travel_to = Some(travel_to);
self.controller.speed_factor = 0.85;
self.controller.speed_factor = 0.70;
});
}
}

View File

@ -95,7 +95,8 @@ impl<'a> System<'a> for Sys {
let body = entity.get_body();
server_emitter.emit(ServerEvent::CreateNpc {
pos: comp::Pos(spawn_pos),
stats: comp::Stats::new("Traveller [rt]".to_string(), body),
stats: comp::Stats::new("Traveller [rt]".to_string(), body)
.with_level(entity.get_level()),
health: comp::Health::new(body, 10),
loadout: entity.get_loadout(&ability_map),
body,

View File

@ -484,14 +484,19 @@ impl<'a> Widget for Map<'a> {
{
continue;
}
// TODO: Pass actual difficulty in here
let dif = 0.0 as u8;
let title = match &site.kind {
SiteKind::Town => "Town",
SiteKind::Dungeon => "Dungeon",
SiteKind::Castle => "Castle",
let title = site.name
.as_ref()
.map(|s| s.as_str())
.unwrap_or_else(|| match &site.kind {
SiteKind::Town => "Town",
SiteKind::Dungeon { .. } => "Dungeon",
SiteKind::Castle => "Castle",
});
let (difficulty, desc) = match &site.kind {
SiteKind::Town => (0, "Town".to_string()),
SiteKind::Dungeon { difficulty } => (*difficulty, format!("Dungeon, difficulty {}", difficulty)),
SiteKind::Castle => (0, "Castle".to_string()),
};
let desc = format!("Difficulty: {}", dif);
let site_btn = Button::image(match &site.kind {
SiteKind::Town => {
if show_towns {
@ -500,7 +505,7 @@ impl<'a> Widget for Map<'a> {
self.imgs.nothing
}
},
SiteKind::Dungeon => {
SiteKind::Dungeon { .. } => {
if show_dungeons {
self.imgs.mmap_site_dungeon
} else {
@ -523,17 +528,17 @@ impl<'a> Widget for Map<'a> {
.w_h(20.0 * 1.2, 20.0 * 1.2)
.hover_image(match &site.kind {
SiteKind::Town => self.imgs.mmap_site_town_hover,
SiteKind::Dungeon => self.imgs.mmap_site_dungeon_hover,
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon_hover,
SiteKind::Castle => self.imgs.mmap_site_castle_hover,
})
.image_color(UI_HIGHLIGHT_0)
.image_color(UI_HIGHLIGHT_0)
.parent(ui.window)
.with_tooltip(
self.tooltip_manager,
title,
&desc,
&site_tooltip,
match dif {
match difficulty {
1 => QUALITY_LOW,
2 => QUALITY_COMMON,
3 => QUALITY_MODERATE,
@ -550,7 +555,7 @@ impl<'a> Widget for Map<'a> {
site_btn.set(state.ids.mmap_site_icons[i], ui);
}
},
SiteKind::Dungeon => {
SiteKind::Dungeon { .. } => {
if show_dungeons {
site_btn.set(state.ids.mmap_site_icons[i], ui);
}
@ -566,7 +571,7 @@ impl<'a> Widget for Map<'a> {
// 0 = towns and places without a difficulty level
if show_difficulty {
let size = 1.8; // Size factor for difficulty indicators
let dif_img = Image::new(match dif {
let dif_img = Image::new(match difficulty {
1 => self.imgs.map_dif_0,
2 => self.imgs.map_dif_1,
3 => self.imgs.map_dif_2,
@ -575,19 +580,19 @@ impl<'a> Widget for Map<'a> {
6 => self.imgs.map_dif_5,
_ => self.imgs.nothing,
})
.mid_top_with_margin_on(state.ids.mmap_site_icons[i], match dif {
.mid_top_with_margin_on(state.ids.mmap_site_icons[i], match difficulty {
6 => -12.0 * size,
_ => -4.0 * size,
})
.w(match dif {
.w(match difficulty {
6 => 12.0 * size,
_ => 4.0 * size * dif as f64,
_ => 4.0 * size * difficulty as f64,
})
.h(match dif {
.h(match difficulty {
6 => 12.0 * size,
_ => 4.0 * size,
})
.color(Some(match dif {
.color(Some(match difficulty {
1 => QUALITY_LOW,
2 => QUALITY_COMMON,
3 => QUALITY_MODERATE,
@ -602,7 +607,7 @@ impl<'a> Widget for Map<'a> {
dif_img.set(state.ids.site_difs[i], ui)
}
},
SiteKind::Dungeon => {
SiteKind::Dungeon { .. } => {
if show_dungeons {
dif_img.set(state.ids.site_difs[i], ui)
}

View File

@ -252,7 +252,7 @@ impl<'a> Widget for MiniMap<'a> {
Image::new(match &site.kind {
SiteKind::Town => self.imgs.mmap_site_town,
SiteKind::Dungeon => self.imgs.mmap_site_dungeon,
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon,
SiteKind::Castle => self.imgs.mmap_site_castle,
})
.x_y_position_relative_to(

View File

@ -108,6 +108,7 @@ impl Civs {
kind,
center: loc,
place,
site_tmp: None,
population: 0.0,
@ -189,7 +190,7 @@ impl Civs {
// Place sites in world
let mut cnt = 0;
for sim_site in this.sites.values() {
for sim_site in this.sites.values_mut() {
cnt += 1;
let wpos = sim_site
.center
@ -209,6 +210,7 @@ impl Civs {
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
},
});
sim_site.site_tmp = Some(site);
let site_ref = &index.sites[site];
let radius_chunks =
@ -371,6 +373,7 @@ impl Civs {
let loc = find_site_loc(ctx, None, 1)?;
self.establish_site(ctx, loc, |place| Site {
kind: SiteKind::Settlement,
site_tmp: None,
center: loc,
place,
@ -796,6 +799,8 @@ pub struct Track {
#[derive(Debug)]
pub struct Site {
pub kind: SiteKind,
// TODO: Remove this field when overhauling
pub site_tmp: Option<Id<crate::site::Site>>,
pub center: Vec2<i32>,
pub place: Id<Place>,

View File

@ -99,14 +99,19 @@ impl World {
.iter()
.map(|(_, site)| {
world_msg::SiteInfo {
name: site.site_tmp.map(|id| index.sites[id].name().to_string()),
// TODO: Probably unify these, at some point
kind: match &site.kind {
civ::SiteKind::Settlement => world_msg::SiteKind::Town,
civ::SiteKind::Dungeon => world_msg::SiteKind::Dungeon,
civ::SiteKind::Dungeon => world_msg::SiteKind::Dungeon {
difficulty: match site.site_tmp.map(|id| &index.sites[id].kind) {
Some(site::SiteKind::Dungeon(d)) => d.difficulty(),
_ => 0,
},
},
civ::SiteKind::Castle => world_msg::SiteKind::Castle,
},
wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
name: None,
}
})
.collect(),
@ -134,6 +139,7 @@ impl World {
let mut sampler = self.sample_blocks();
let chunk_wpos2d = chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
let chunk_center_wpos2d = chunk_wpos2d + TerrainChunkSize::RECT_SIZE.map(|e| e as i32 / 2);
let grid_border = 4;
let zcache_grid = Grid::populate_from(
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
@ -176,7 +182,11 @@ impl World {
};
let meta = TerrainChunkMeta::new(
sim_chunk.get_name(&self.sim),
sim_chunk.sites
.iter()
.filter(|id| index.sites[**id].get_origin().distance_squared(chunk_center_wpos2d) as f32 <= index.sites[**id].radius().powf(2.0))
.min_by_key(|id| index.sites[**id].get_origin().distance_squared(chunk_center_wpos2d))
.map(|id| index.sites[*id].name().to_string()),
sim_chunk.get_biome(),
sim_chunk.alt,
sim_chunk.tree_density,

View File

@ -2313,19 +2313,6 @@ impl SimChunk {
pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }
pub fn get_name(&self, _world: &WorldSim) -> Option<String> {
// TODO
None
/*
if let Some(loc) = &self.location {
Some(world.locations[loc.loc_idx].name().to_string())
} else {
None
}
*/
}
pub fn get_biome(&self) -> BiomeKind {
if self.alt < CONFIG.sea_level {
BiomeKind::Ocean

View File

@ -2,9 +2,12 @@ use super::SpawnRules;
use crate::{
column::ColumnSample,
sim::WorldSim,
site::settlement::building::{
archetype::keep::{Attr, FlagColor, Keep as KeepArchetype, StoneColor},
Archetype, Ori,
site::{
settlement::building::{
archetype::keep::{Attr, FlagColor, Keep as KeepArchetype, StoneColor},
Archetype, Ori,
},
namegen::NameGen,
},
IndexRef,
};
@ -32,6 +35,7 @@ struct Tower {
}
pub struct Castle {
name: String,
origin: Vec2<i32>,
//seed: u32,
radius: i32,
@ -64,6 +68,17 @@ impl Castle {
let radius = 150;
let this = Self {
name: {
let name = NameGen::location(ctx.rng).generate();
match ctx.rng.gen_range(0, 6) {
0 => format!("Fort {}", name),
1 => format!("{} Citadel", name),
2 => format!("{} Castle", name),
3 => format!("{} Stronghold", name),
4 => format!("{} Fortress", name),
_ => format!("{} Keep", name),
}
},
origin: wpos,
// alt: ctx
// .sim
@ -141,6 +156,10 @@ impl Castle {
this
}
pub fn name(&self) -> &str {
&self.name
}
pub fn contains_point(&self, wpos: Vec2<i32>) -> bool {
let lpos = wpos - self.origin;
for i in 0..self.towers.len() {
@ -160,7 +179,7 @@ impl Castle {
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
pub fn radius(&self) -> f32 { 1200.0 }
pub fn radius(&self) -> f32 { 200.0 }
#[allow(clippy::needless_update)] // TODO: Pending review in #587
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {

View File

@ -3,7 +3,7 @@ use crate::{
block::block_from_structure,
column::ColumnSample,
sim::WorldSim,
site::BlockMask,
site::{BlockMask, namegen::NameGen},
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
IndexRef,
};
@ -26,12 +26,14 @@ use std::sync::Arc;
use vek::*;
pub struct Dungeon {
name: String,
origin: Vec2<i32>,
alt: i32,
seed: u32,
#[allow(dead_code)]
noise: RandomField,
floors: Vec<Floor>,
difficulty: u32,
}
pub struct GenCtx<'a, R: Rng> {
@ -52,7 +54,18 @@ impl Dungeon {
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
let mut ctx = GenCtx { sim, rng };
let difficulty = ctx.rng.gen_range(0, 7);
let this = Self {
name: {
let name = NameGen::location(ctx.rng).generate();
match ctx.rng.gen_range(0, 5) {
0 => format!("{} Dungeon", name),
1 => format!("{} Lair", name),
2 => format!("{} Crib", name),
3 => format!("{} Catacombs", name),
_ => format!("{} Pit", name),
}
},
origin: wpos - TILE_SIZE / 2,
alt: ctx
.sim
@ -63,19 +76,24 @@ impl Dungeon {
noise: RandomField::new(ctx.rng.gen()),
floors: (0..LEVELS)
.scan(Vec2::zero(), |stair_tile, level| {
let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level as i32);
let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level as i32, difficulty);
*stair_tile = st;
Some(floor)
})
.collect(),
difficulty,
};
this
}
pub fn name(&self) -> &str {
&self.name
}
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
pub fn radius(&self) -> f32 { 1200.0 }
pub fn radius(&self) -> f32 { 200.0 }
#[allow(clippy::needless_update)] // TODO: Pending review in #587
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
@ -85,6 +103,10 @@ impl Dungeon {
}
}
pub fn difficulty(&self) -> u32 {
self.difficulty
}
pub fn apply_to<'a>(
&'a self,
index: IndexRef,
@ -227,6 +249,7 @@ pub struct Room {
area: Rect<i32, i32>,
height: i32,
pillars: Option<i32>, // Pillars with the given separation
difficulty: u32,
}
struct Floor {
@ -238,6 +261,7 @@ struct Floor {
#[allow(dead_code)]
stair_tile: Vec2<i32>,
final_level: bool,
difficulty: u32,
}
const FLOOR_SIZE: Vec2<i32> = Vec2::new(18, 18);
@ -247,6 +271,7 @@ impl Floor {
ctx: &mut GenCtx<impl Rng>,
stair_tile: Vec2<i32>,
level: i32,
difficulty: u32,
) -> (Self, Vec2<i32>) {
let final_level = level == LEVELS as i32 - 1;
@ -271,6 +296,7 @@ impl Floor {
hollow_depth: 30,
stair_tile: new_stair_tile - tile_offset,
final_level,
difficulty,
};
const STAIR_ROOM_HEIGHT: i32 = 13;
@ -284,6 +310,7 @@ impl Floor {
area: Rect::from((stair_tile - tile_offset - 1, Extent2::broadcast(3))),
height: STAIR_ROOM_HEIGHT,
pillars: None,
difficulty,
});
this.tiles
.set(stair_tile - tile_offset, Tile::UpStair(upstair_room));
@ -298,6 +325,7 @@ impl Floor {
area: Rect::from((new_stair_tile - tile_offset - 4, Extent2::broadcast(9))),
height: 30,
pillars: Some(2),
difficulty,
});
} else {
// Create downstairs room
@ -310,6 +338,7 @@ impl Floor {
area: Rect::from((new_stair_tile - tile_offset - 1, Extent2::broadcast(3))),
height: STAIR_ROOM_HEIGHT,
pillars: None,
difficulty,
});
this.tiles.set(
new_stair_tile - tile_offset,
@ -393,6 +422,7 @@ impl Floor {
} else {
None
},
difficulty: self.difficulty,
}),
};
}
@ -507,6 +537,7 @@ impl Floor {
_ => "common.loot_tables.loot_table_cultists",
});
let chosen = chosen.choose();
let is_giant = RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos), 0.2) && !room.boss;
let entity = EntityInfo::at(
tile_wcenter.map(|e| e as f32)
// Randomly displace them a little
@ -514,7 +545,7 @@ impl Floor {
.map(|e| (RandomField::new(room.seed.wrapping_add(10 + e)).get(Vec3::from(tile_pos)) % 32) as i32 - 16)
.map(|e| e as f32 / 16.0),
)
.do_if(RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos), 0.2) && !room.boss, |e| e.into_giant())
.do_if(is_giant, |e| e.into_giant())
.with_alignment(comp::Alignment::Enemy)
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
.with_name("Cultist Acolyte")
@ -526,7 +557,11 @@ impl Floor {
3 => "common.items.npc_weapons.hammer.cultist_purp_2h-0",
4 => "common.items.npc_weapons.staff.cultist_staff",
_ => "common.items.npc_weapons.bow.horn_longbow-0",
}));
}))
.with_level(dynamic_rng.gen_range(
(room.difficulty as f32).powf(1.25) + 3.0,
(room.difficulty as f32).powf(1.5) + 4.0,
).round() as u32);
supplement.add_entity(entity);
}
@ -558,7 +593,11 @@ impl Floor {
&comp::golem::Species::StoneGolem,
)))
.with_name("Stonework Defender".to_string())
.with_loot_drop(comp::Item::new_from_asset_expect(chosen));
.with_loot_drop(comp::Item::new_from_asset_expect(chosen))
.with_level(dynamic_rng.gen_range(
(room.difficulty as f32).powf(1.25) + 3.0,
(room.difficulty as f32).powf(1.5) + 4.0,
).round() as u32 * 5);
supplement.add_entity(entity);
}

View File

@ -1,6 +1,7 @@
mod block_mask;
mod castle;
mod dungeon;
mod namegen;
pub mod economy;
mod settlement;
@ -92,6 +93,14 @@ impl Site {
}
}
pub fn name(&self) -> &str {
match &self.kind {
SiteKind::Settlement(s) => s.name(),
SiteKind::Dungeon(d) => d.name(),
SiteKind::Castle(c) => c.name(),
}
}
pub fn apply_to<'a>(
&'a self,
index: IndexRef,

55
world/src/site/namegen.rs Normal file
View File

@ -0,0 +1,55 @@
use rand::prelude::*;
pub struct NameGen<'a, R: Rng> {
// 2..
pub approx_syllables: usize,
rng: &'a mut R,
}
impl<'a, R: Rng> NameGen<'a, R> {
pub fn location(rng: &'a mut R) -> Self {
Self {
approx_syllables: rng.gen_range(1, 4),
rng,
}
}
pub fn generate(self) -> String {
let cons = vec![
"d", "f", "ph", "r", "st", "t", "s", "p", "sh", "th", "br", "tr", "m", "k", "st", "w", "y",
];
let mut start = cons.clone();
start.extend(vec![
"cr", "thr", "str", "br", "ivy", "est", "ost", "ing", "kr", "in", "on", "tr", "tw", "wh",
"eld", "ar", "or", "ear", "ir",
]);
let mut middle = cons.clone();
middle.extend(vec!["tt"]);
let vowel = vec!["o", "e", "a", "i", "u", "au", "ee", "ow", "ay", "ey", "oe"];
let end = vec![
"et", "ige", "age", "ist", "en", "on", "og", "end", "ind", "ock", "een", "edge", "ist",
"ed", "est", "eed", "ast", "olt", "ey", "ean", "ead", "onk", "ink", "eon", "er", "ow",
"cot", "in", "on",
];
let mut name = String::new();
name += start.choose(self.rng).unwrap();
for _ in 0..self.approx_syllables.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()
}
}

View File

@ -10,6 +10,7 @@ use crate::{
column::ColumnSample,
sim::WorldSim,
util::{RandomField, Sampler, StructureGen2d},
site::namegen::NameGen,
IndexRef,
};
use common::{
@ -139,6 +140,7 @@ impl Structure {
}
pub struct Settlement {
name: String,
seed: u32,
origin: Vec2<i32>,
land: Land,
@ -162,6 +164,7 @@ impl Settlement {
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
let mut ctx = GenCtx { sim, rng };
let mut this = Self {
name: NameGen::location(ctx.rng).generate(),
seed: ctx.rng.gen(),
origin: wpos,
land: Land::new(ctx.rng),
@ -185,6 +188,10 @@ impl Settlement {
this
}
pub fn name(&self) -> &str {
&self.name
}
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
/// Designate hazardous terrain based on world data
@ -529,7 +536,7 @@ impl Settlement {
}
}
pub fn radius(&self) -> f32 { 1200.0 }
pub fn radius(&self) -> f32 { 400.0 }
#[allow(clippy::needless_update)] // TODO: Pending review in #587
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {