mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added site names, dungeon difficulty, better explosions
This commit is contained in:
parent
5b907ede9e
commit
7850b0bcaf
@ -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,
|
||||
}),
|
||||
|
@ -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 {
|
||||
|
@ -134,6 +134,6 @@ pub struct SiteInfo {
|
||||
#[repr(u8)]
|
||||
pub enum SiteKind {
|
||||
Town,
|
||||
Dungeon,
|
||||
Dungeon { difficulty: u32 },
|
||||
Castle,
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
})
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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>,
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
55
world/src/site/namegen.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user