Fixed figure AO (still need to do sprite AO), added more control over NPC spawning, loot in dungeons

This commit is contained in:
Joshua Barretto 2020-04-17 21:58:36 +01:00
parent 3042a09dfd
commit e3ebdc56b3
11 changed files with 416 additions and 333 deletions

View File

@ -4,6 +4,7 @@
in vec3 f_pos; in vec3 f_pos;
in vec3 f_col; in vec3 f_col;
in float f_ao;
flat in vec3 f_norm; flat in vec3 f_norm;
layout (std140) layout (std140)
@ -39,6 +40,12 @@ void main() {
vec3 point_light = light_at(f_pos, f_norm); vec3 point_light = light_at(f_pos, f_norm);
light += point_light; light += point_light;
diffuse_light += point_light; diffuse_light += point_light;
float ao = pow(f_ao, 0.5) * 0.85 + 0.15;
ambient_light *= ao;
diffuse_light *= ao;
vec3 surf_color = illuminate(srgb_to_linear(model_col.rgb * f_col), light, diffuse_light, ambient_light); vec3 surf_color = illuminate(srgb_to_linear(model_col.rgb * f_col), light, diffuse_light, ambient_light);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);

View File

@ -5,6 +5,7 @@
in vec3 v_pos; in vec3 v_pos;
in vec3 v_norm; in vec3 v_norm;
in vec3 v_col; in vec3 v_col;
in float v_ao;
in uint v_bone_idx; in uint v_bone_idx;
layout (std140) layout (std140)
@ -27,6 +28,7 @@ uniform u_bones {
out vec3 f_pos; out vec3 f_pos;
out vec3 f_col; out vec3 f_col;
out float f_ao;
flat out vec3 f_norm; flat out vec3 f_norm;
void main() { void main() {
@ -39,6 +41,8 @@ void main() {
f_col = v_col; f_col = v_col;
f_ao = v_ao;
// Calculate normal here rather than for each pixel in the fragment shader // Calculate normal here rather than for each pixel in the fragment shader
f_norm = normalize(( f_norm = normalize((
combined_mat * combined_mat *

View File

@ -19,6 +19,10 @@ out vec4 tgt_color;
#include <sky.glsl> #include <sky.glsl>
#include <light.glsl> #include <light.glsl>
float vmin(vec3 v) {
return min(v.x, min(v.y, v.z));
}
void main() { void main() {
// First 3 normals are negative, next 3 are positive // First 3 normals are negative, next 3 are positive
vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1));
@ -30,7 +34,7 @@ void main() {
// Use an array to avoid conditional branching // Use an array to avoid conditional branching
vec3 f_norm = normals[(f_pos_norm >> 29) & 0x7u]; vec3 f_norm = normals[(f_pos_norm >> 29) & 0x7u];
float ao = pow(f_ao, 0.6) * 0.9 + 0.1; float ao = pow(f_ao, 0.5) * 0.9 + 0.1;
vec3 light, diffuse_light, ambient_light; vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);

View File

@ -1,14 +1,44 @@
use vek::*; use vek::*;
use crate::comp::{Alignment};
pub enum EntityKind {
Enemy,
Boss,
Waypoint,
}
pub struct EntityInfo { pub struct EntityInfo {
pub pos: Vec3<f32>, pub pos: Vec3<f32>,
pub kind: EntityKind, pub is_waypoint: bool, // Edge case, overrides everything else
pub is_giant: bool,
pub alignment: Alignment,
}
impl EntityInfo {
pub fn at(pos: Vec3<f32>) -> Self {
Self {
pos,
is_waypoint: false,
is_giant: false,
alignment: Alignment::Wild,
}
}
pub fn do_if(mut self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self {
if cond {
self = f(self);
}
self
}
pub fn into_waypoint(mut self) -> Self {
self.is_waypoint = true;
self
}
pub fn into_giant(mut self) -> Self {
self.is_giant = true;
self
}
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
} }
#[derive(Default)] #[derive(Default)]

View File

@ -1,6 +1,7 @@
use std::{ use std::{
fmt, fmt,
hash, hash,
ops::{Index, IndexMut},
cmp::{PartialEq, Eq}, cmp::{PartialEq, Eq},
marker::PhantomData, marker::PhantomData,
}; };
@ -65,3 +66,12 @@ impl<T> Store<T> {
id id
} }
} }
impl<T> Index<Id<T>> for Store<T> {
type Output = T;
fn index(&self, id: Id<T>) -> &Self::Output { self.get(id) }
}
impl<T> IndexMut<Id<T>> for Store<T> {
fn index_mut(&mut self, id: Id<T>) -> &mut Self::Output { self.get_mut(id) }
}

View File

@ -4,7 +4,6 @@ use common::{
assets, assets,
comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos}, comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
generation::EntityKind,
msg::ServerMsg, msg::ServerMsg,
npc::{self, NPC_NAMES}, npc::{self, NPC_NAMES},
state::TerrainChanges, state::TerrainChanges,
@ -101,295 +100,297 @@ impl<'a> System<'a> for Sys {
// Handle chunk supplement // Handle chunk supplement
for entity in supplement.entities { for entity in supplement.entities {
if let EntityKind::Waypoint = entity.kind { if entity.is_waypoint {
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos)); server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
} else { continue;
fn get_npc_name< }
'a,
Species,
SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>,
>(
body_data: &'a comp::BodyData<npc::BodyNames, SpeciesData>,
species: Species,
) -> &'a str {
&body_data.species[&species].generic
}
const SPAWN_NPCS: &'static [fn() -> ( fn get_npc_name<
String, 'a,
comp::Body, Species,
Option<comp::Item>, SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>,
comp::Alignment, >(
)] = &[ body_data: &'a comp::BodyData<npc::BodyNames, SpeciesData>,
(|| { species: Species,
let body = comp::humanoid::Body::random(); ) -> &'a str {
( &body_data.species[&species].generic
format!( }
"{} Traveler",
get_npc_name(&NPC_NAMES.humanoid, body.race)
),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned(
"common.items.weapons.starter_axe",
)),
comp::Alignment::Npc,
)
}) as _,
(|| {
let body = comp::humanoid::Body::random();
(
format!("{} Bandit", get_npc_name(&NPC_NAMES.humanoid, body.race)),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned(
"common.items.weapons.short_sword_0",
)),
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::quadruped_medium::Body::random();
(
get_npc_name(&NPC_NAMES.quadruped_medium, body.species).into(),
comp::Body::QuadrupedMedium(body),
None,
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::bird_medium::Body::random();
(
get_npc_name(&NPC_NAMES.bird_medium, body.species).into(),
comp::Body::BirdMedium(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::critter::Body::random();
(
get_npc_name(&NPC_NAMES.critter, body.species).into(),
comp::Body::Critter(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::quadruped_small::Body::random();
(
get_npc_name(&NPC_NAMES.quadruped_small, body.species).into(),
comp::Body::QuadrupedSmall(body),
None,
comp::Alignment::Wild,
)
}),
];
let (name, mut body, main, mut alignment) = SPAWN_NPCS
.choose(&mut rand::thread_rng())
.expect("SPAWN_NPCS is nonempty")(
);
let mut stats = comp::Stats::new(name, body);
let active_item = const SPAWN_NPCS: &'static [fn() -> (
if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) { String,
let mut abilities = tool.get_abilities(); comp::Body,
let mut ability_drain = abilities.drain(..); Option<comp::Item>,
comp::Alignment,
)] = &[
(|| {
let body = comp::humanoid::Body::random();
(
format!(
"{} Traveler",
get_npc_name(&NPC_NAMES.humanoid, body.race)
),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned(
"common.items.weapons.starter_axe",
)),
comp::Alignment::Npc,
)
}) as _,
(|| {
let body = comp::humanoid::Body::random();
(
format!("{} Bandit", get_npc_name(&NPC_NAMES.humanoid, body.race)),
comp::Body::Humanoid(body),
Some(assets::load_expect_cloned(
"common.items.weapons.short_sword_0",
)),
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::quadruped_medium::Body::random();
(
get_npc_name(&NPC_NAMES.quadruped_medium, body.species).into(),
comp::Body::QuadrupedMedium(body),
None,
comp::Alignment::Enemy,
)
}) as _,
(|| {
let body = comp::bird_medium::Body::random();
(
get_npc_name(&NPC_NAMES.bird_medium, body.species).into(),
comp::Body::BirdMedium(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::critter::Body::random();
(
get_npc_name(&NPC_NAMES.critter, body.species).into(),
comp::Body::Critter(body),
None,
comp::Alignment::Wild,
)
}) as _,
(|| {
let body = comp::quadruped_small::Body::random();
(
get_npc_name(&NPC_NAMES.quadruped_small, body.species).into(),
comp::Body::QuadrupedSmall(body),
None,
comp::Alignment::Wild,
)
}),
];
let (name, mut body, main, mut alignment) = SPAWN_NPCS
.choose(&mut rand::thread_rng())
.expect("SPAWN_NPCS is nonempty")(
);
let mut stats = comp::Stats::new(name, body);
main.map(|item| comp::ItemConfig { let alignment = entity.alignment;
item,
ability1: ability_drain.next(),
ability2: ability_drain.next(),
ability3: ability_drain.next(),
block_ability: None,
dodge_ability: Some(comp::CharacterAbility::Roll),
})
} else {
Some(ItemConfig {
// We need the empty item so npcs can attack
item: Item::empty(),
ability1: Some(CharacterAbility::BasicMelee {
energy_cost: 0,
buildup_duration: Duration::from_millis(0),
recover_duration: Duration::from_millis(400),
base_healthchange: -4,
range: 3.5,
max_angle: 60.0,
}),
ability2: None,
ability3: None,
block_ability: None,
dodge_ability: None,
})
};
let mut loadout = match alignment { let active_item =
comp::Alignment::Npc => comp::Loadout { if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) {
active_item, let mut abilities = tool.get_abilities();
second_item: None, let mut ability_drain = abilities.drain(..);
shoulder: Some(assets::load_expect_cloned(
"common.items.armor.shoulder.leather_0", main.map(|item| comp::ItemConfig {
)), item,
chest: Some(assets::load_expect_cloned( ability1: ability_drain.next(),
"common.items.armor.chest.leather_0", ability2: ability_drain.next(),
)), ability3: ability_drain.next(),
belt: Some(assets::load_expect_cloned( block_ability: None,
"common.items.armor.belt.plate_0", dodge_ability: Some(comp::CharacterAbility::Roll),
)), })
hand: Some(assets::load_expect_cloned( } else {
"common.items.armor.hand.plate_0", Some(ItemConfig {
)), // We need the empty item so npcs can attack
pants: Some(assets::load_expect_cloned( item: Item::empty(),
"common.items.armor.pants.plate_green_0", ability1: Some(CharacterAbility::BasicMelee {
)), energy_cost: 0,
foot: Some(assets::load_expect_cloned( buildup_duration: Duration::from_millis(0),
"common.items.armor.foot.leather_0", recover_duration: Duration::from_millis(400),
)), base_healthchange: -4,
back: None, range: 3.5,
ring: None, max_angle: 60.0,
neck: None, }),
lantern: None, ability2: None,
head: None, ability3: None,
tabard: None, block_ability: None,
}, dodge_ability: None,
comp::Alignment::Enemy => comp::Loadout { })
active_item,
second_item: None,
shoulder: Some(assets::load_expect_cloned(
"common.items.armor.shoulder.leather_0",
)),
chest: Some(assets::load_expect_cloned(
"common.items.armor.chest.plate_green_0",
)),
belt: Some(assets::load_expect_cloned(
"common.items.armor.belt.plate_0",
)),
hand: Some(assets::load_expect_cloned(
"common.items.armor.hand.plate_0",
)),
pants: Some(assets::load_expect_cloned(
"common.items.armor.pants.plate_green_0",
)),
foot: Some(assets::load_expect_cloned(
"common.items.armor.foot.plate_0",
)),
back: None,
ring: None,
neck: None,
lantern: None,
head: None,
tabard: None,
},
_ => comp::Loadout {
active_item,
second_item: None,
shoulder: None,
chest: None,
belt: None,
hand: None,
pants: None,
foot: None,
back: None,
ring: None,
neck: None,
lantern: None,
head: None,
tabard: None,
},
}; };
let mut scale = 1.0; let mut loadout = match alignment {
comp::Alignment::Npc => comp::Loadout {
active_item,
second_item: None,
shoulder: Some(assets::load_expect_cloned(
"common.items.armor.shoulder.leather_0",
)),
chest: Some(assets::load_expect_cloned(
"common.items.armor.chest.leather_0",
)),
belt: Some(assets::load_expect_cloned(
"common.items.armor.belt.plate_0",
)),
hand: Some(assets::load_expect_cloned(
"common.items.armor.hand.plate_0",
)),
pants: Some(assets::load_expect_cloned(
"common.items.armor.pants.plate_green_0",
)),
foot: Some(assets::load_expect_cloned(
"common.items.armor.foot.leather_0",
)),
back: None,
ring: None,
neck: None,
lantern: None,
head: None,
tabard: None,
},
comp::Alignment::Enemy => comp::Loadout {
active_item,
second_item: None,
shoulder: Some(assets::load_expect_cloned(
"common.items.armor.shoulder.leather_0",
)),
chest: Some(assets::load_expect_cloned(
"common.items.armor.chest.plate_green_0",
)),
belt: Some(assets::load_expect_cloned(
"common.items.armor.belt.plate_0",
)),
hand: Some(assets::load_expect_cloned(
"common.items.armor.hand.plate_0",
)),
pants: Some(assets::load_expect_cloned(
"common.items.armor.pants.plate_green_0",
)),
foot: Some(assets::load_expect_cloned(
"common.items.armor.foot.plate_0",
)),
back: None,
ring: None,
neck: None,
lantern: None,
head: None,
tabard: None,
},
_ => comp::Loadout {
active_item,
second_item: None,
shoulder: None,
chest: None,
belt: None,
hand: None,
pants: None,
foot: None,
back: None,
ring: None,
neck: None,
lantern: None,
head: None,
tabard: None,
},
};
// TODO: Remove this and implement scaling or level depending on stuff like let mut scale = 1.0;
// species instead
stats.level.set_level(rand::thread_rng().gen_range(1, 9));
// Replace stuff if it's a boss // TODO: Remove this and implement scaling or level depending on stuff like
if let EntityKind::Boss = entity.kind { // species instead
if rand::random::<f32>() < 0.65 { stats.level.set_level(rand::thread_rng().gen_range(1, 9));
let body_new = comp::humanoid::Body::random();
body = comp::Body::Humanoid(body_new); // Replace stuff if it's a boss
alignment = comp::Alignment::Npc; if entity.is_giant {
stats = comp::Stats::new( if rand::random::<f32>() < 0.65 {
format!( let body_new = comp::humanoid::Body::random();
"Fearless Giant {}", body = comp::Body::Humanoid(body_new);
get_npc_name(&NPC_NAMES.humanoid, body_new.race) stats = comp::Stats::new(
), format!(
body, "Fearless Giant {}",
); get_npc_name(&NPC_NAMES.humanoid, body_new.race)
} ),
loadout = comp::Loadout { body,
active_item: Some(comp::ItemConfig { );
item: assets::load_expect_cloned( }
"common.items.weapons.zweihander_sword_0", loadout = comp::Loadout {
), active_item: Some(comp::ItemConfig {
ability1: Some(CharacterAbility::BasicMelee { item: assets::load_expect_cloned(
energy_cost: 0, "common.items.weapons.zweihander_sword_0",
buildup_duration: Duration::from_millis(800), ),
recover_duration: Duration::from_millis(200), ability1: Some(CharacterAbility::BasicMelee {
base_healthchange: -13, energy_cost: 0,
range: 3.5, buildup_duration: Duration::from_millis(800),
max_angle: 60.0, recover_duration: Duration::from_millis(200),
}), base_healthchange: -13,
ability2: None, range: 3.5,
ability3: None, max_angle: 60.0,
block_ability: None,
dodge_ability: None,
}), }),
second_item: None, ability2: None,
shoulder: Some(assets::load_expect_cloned( ability3: None,
"common.items.armor.shoulder.plate_0", block_ability: None,
)), dodge_ability: None,
chest: Some(assets::load_expect_cloned( }),
"common.items.armor.chest.plate_green_0", second_item: None,
)), shoulder: Some(assets::load_expect_cloned(
belt: Some(assets::load_expect_cloned( "common.items.armor.shoulder.plate_0",
"common.items.armor.belt.plate_0", )),
)), chest: Some(assets::load_expect_cloned(
hand: Some(assets::load_expect_cloned( "common.items.armor.chest.plate_green_0",
"common.items.armor.hand.plate_0", )),
)), belt: Some(assets::load_expect_cloned(
pants: Some(assets::load_expect_cloned( "common.items.armor.belt.plate_0",
"common.items.armor.pants.plate_green_0", )),
)), hand: Some(assets::load_expect_cloned(
foot: Some(assets::load_expect_cloned( "common.items.armor.hand.plate_0",
"common.items.armor.foot.plate_0", )),
)), pants: Some(assets::load_expect_cloned(
back: None, "common.items.armor.pants.plate_green_0",
ring: None, )),
neck: None, foot: Some(assets::load_expect_cloned(
lantern: None, "common.items.armor.foot.plate_0",
head: None, )),
tabard: None, back: None,
}; ring: None,
neck: None,
lantern: None,
head: None,
tabard: None,
};
stats.level.set_level(rand::thread_rng().gen_range(30, 35)); stats.level.set_level(rand::thread_rng().gen_range(30, 35));
scale = 2.0 + rand::random::<f32>(); scale = 2.0 + rand::random::<f32>();
}
stats.update_max_hp();
stats
.health
.set_to(stats.health.maximum(), comp::HealthSource::Revive);
// TODO: This code sets an appropriate base_damage for the enemy. This doesn't
// work because the damage is now saved in an ability
/*
if let Some(item::ItemKind::Tool(item::ToolData { base_damage, .. })) =
&mut loadout.active_item.map(|i| i.item.kind)
{
*base_damage = stats.level.level() as u32 * 3;
}
*/
server_emitter.emit(ServerEvent::CreateNpc {
pos: Pos(entity.pos),
stats,
loadout,
body,
alignment,
agent: comp::Agent::default().with_patrol_origin(entity.pos),
scale: comp::Scale(scale),
})
} }
stats.update_max_hp();
stats
.health
.set_to(stats.health.maximum(), comp::HealthSource::Revive);
// TODO: This code sets an appropriate base_damage for the enemy. This doesn't
// work because the damage is now saved in an ability
/*
if let Some(item::ItemKind::Tool(item::ToolData { base_damage, .. })) =
&mut loadout.active_item.map(|i| i.item.kind)
{
*base_damage = stats.level.level() as u32 * 3;
}
*/
server_emitter.emit(ServerEvent::CreateNpc {
pos: Pos(entity.pos),
stats,
loadout,
body,
alignment,
agent: comp::Agent::default().with_patrol_origin(entity.pos),
scale: comp::Scale(scale),
})
} }
} }

View File

@ -30,11 +30,12 @@ impl Meshable<FigurePipeline, FigurePipeline> for Segment {
faces_to_make(self, pos, true, |vox| vox.is_empty()), faces_to_make(self, pos, true, |vox| vox.is_empty()),
offs + pos.map(|e| e as f32), offs + pos.map(|e| e as f32),
&[[[Rgba::from_opaque(col); 3]; 3]; 3], &[[[Rgba::from_opaque(col); 3]; 3]; 3],
|origin, norm, col, ao, light| { |origin, norm, col, light, ao| {
FigureVertex::new( FigureVertex::new(
origin, origin,
norm, norm,
linear_to_srgb(srgb_to_linear(col) * light.min(ao.powf(0.5) * 0.75 + 0.25)), linear_to_srgb(srgb_to_linear(col) * light),
ao,
0, 0,
) )
}, },
@ -83,7 +84,7 @@ impl Meshable<SpritePipeline, SpritePipeline> for Segment {
faces_to_make(self, pos, true, |vox| vox.is_empty()), faces_to_make(self, pos, true, |vox| vox.is_empty()),
offs + pos.map(|e| e as f32), offs + pos.map(|e| e as f32),
&[[[Rgba::from_opaque(col); 3]; 3]; 3], &[[[Rgba::from_opaque(col); 3]; 3]; 3],
|origin, norm, col, ao, light| { |origin, norm, col, light, ao| {
SpriteVertex::new( SpriteVertex::new(
origin, origin,
norm, norm,

View File

@ -14,6 +14,7 @@ gfx_defines! {
pos: [f32; 3] = "v_pos", pos: [f32; 3] = "v_pos",
norm: [f32; 3] = "v_norm", norm: [f32; 3] = "v_norm",
col: [f32; 3] = "v_col", col: [f32; 3] = "v_col",
ao: f32 = "v_ao",
bone_idx: u8 = "v_bone_idx", bone_idx: u8 = "v_bone_idx",
} }
@ -44,11 +45,12 @@ gfx_defines! {
} }
impl Vertex { impl Vertex {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>, bone_idx: u8) -> Self { pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>, ao: f32, bone_idx: u8) -> Self {
Self { Self {
pos: pos.into_array(), pos: pos.into_array(),
col: col.into_array(), col: col.into_array(),
norm: norm.into_array(), norm: norm.into_array(),
ao,
bone_idx, bone_idx,
} }
} }

View File

@ -20,7 +20,8 @@ use crate::{
util::{Grid, Sampler}, util::{Grid, Sampler},
}; };
use common::{ use common::{
generation::{ChunkSupplement, EntityInfo, EntityKind}, generation::{ChunkSupplement, EntityInfo},
comp::{Alignment},
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, Vox, WriteVol}, vol::{ReadVol, RectVolSize, Vox, WriteVol},
}; };
@ -173,31 +174,28 @@ impl World {
(Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5 (Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5
}; };
let mut rng = rand::thread_rng();
const SPAWN_RATE: f32 = 0.1; const SPAWN_RATE: f32 = 0.1;
const BOSS_RATE: f32 = 0.03; const BOSS_RATE: f32 = 0.03;
let mut supplement = ChunkSupplement { let mut supplement = ChunkSupplement {
entities: if rand::thread_rng().gen::<f32>() < SPAWN_RATE entities: if rng.gen::<f32>() < SPAWN_RATE
&& sim_chunk.chaos < 0.5 && sim_chunk.chaos < 0.5
&& !sim_chunk.is_underwater() && !sim_chunk.is_underwater()
{ {
vec![EntityInfo { let entity = EntityInfo::at(gen_entity_pos())
pos: gen_entity_pos(), .with_alignment(Alignment::Wild)
kind: if rand::thread_rng().gen::<f32>() < BOSS_RATE { .do_if(rng.gen(), |e| e.into_giant());
EntityKind::Boss
} else { vec![entity]
EntityKind::Enemy
},
}]
} else { } else {
Vec::new() Vec::new()
}, },
}; };
if sim_chunk.contains_waypoint { if sim_chunk.contains_waypoint {
supplement.add_entity(EntityInfo { supplement.add_entity(EntityInfo::at(gen_entity_pos())
pos: gen_entity_pos(), .into_waypoint());
kind: EntityKind::Waypoint,
});
} }
// Apply site supplementary information // Apply site supplementary information

View File

@ -12,7 +12,8 @@ use common::{
terrain::{Block, BlockKind, TerrainChunkSize}, terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox}, vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox},
store::{Id, Store}, store::{Id, Store},
generation::{ChunkSupplement, EntityInfo, EntityKind}, generation::{ChunkSupplement, EntityInfo},
comp::{Alignment},
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use rand::prelude::*; use rand::prelude::*;
@ -159,7 +160,7 @@ const TILE_SIZE: i32 = 13;
pub enum Tile { pub enum Tile {
UpStair, UpStair,
DownStair, DownStair,
Room, Room(Id<Room>),
Tunnel, Tunnel,
Solid, Solid,
} }
@ -169,17 +170,24 @@ impl Tile {
match self { match self {
Tile::UpStair => true, Tile::UpStair => true,
Tile::DownStair => true, Tile::DownStair => true,
Tile::Room => true, Tile::Room(_) => true,
Tile::Tunnel => true, Tile::Tunnel => true,
_ => false, _ => false,
} }
} }
} }
pub struct Room {
seed: u32,
enemies: bool,
loot_density: f32,
area: Rect<i32, i32>,
}
pub struct Floor { pub struct Floor {
tile_offset: Vec2<i32>, tile_offset: Vec2<i32>,
tiles: Grid<Tile>, tiles: Grid<Tile>,
rooms: Vec<Rect<i32, i32>>, rooms: Store<Room>,
solid_depth: i32, solid_depth: i32,
hollow_depth: i32, hollow_depth: i32,
stair_tile: Vec2<i32>, stair_tile: Vec2<i32>,
@ -199,7 +207,7 @@ impl Floor {
let mut this = Floor { let mut this = Floor {
tile_offset, tile_offset,
tiles: Grid::new(FLOOR_SIZE, Tile::Solid), tiles: Grid::new(FLOOR_SIZE, Tile::Solid),
rooms: Vec::new(), rooms: Store::default(),
solid_depth: if level == 0 { solid_depth: if level == 0 {
80 80
} else { } else {
@ -208,11 +216,11 @@ impl Floor {
hollow_depth: 13, hollow_depth: 13,
stair_tile: new_stair_tile - tile_offset, stair_tile: new_stair_tile - tile_offset,
}; };
this.create_rooms(ctx, 10); this.create_rooms(ctx, level, 10);
// Create routes between all rooms // Create routes between all rooms
let rooms = this.rooms.clone(); let room_areas = this.rooms.iter().map(|r| r.area).collect::<Vec<_>>();
for a in rooms.iter() { for a in room_areas.iter() {
for b in rooms.iter() { for b in room_areas.iter() {
this.create_route(ctx, a.center(), b.center(), true); this.create_route(ctx, a.center(), b.center(), true);
} }
} }
@ -224,35 +232,40 @@ impl Floor {
(this, new_stair_tile) (this, new_stair_tile)
} }
fn create_rooms(&mut self, ctx: &mut GenCtx<impl Rng>, n: usize) { fn create_rooms(&mut self, ctx: &mut GenCtx<impl Rng>, level: i32, n: usize) {
let dim_limits = (3, 8); let dim_limits = (3, 8);
for _ in 0..n { for _ in 0..n {
let room = match attempt(30, || { let area = match attempt(30, || {
let sz = Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1)); let sz = Vec2::<i32>::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1));
let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| ctx.rng.gen_range(0, floor_sz + 1 - room_sz)); let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| ctx.rng.gen_range(0, floor_sz + 1 - room_sz));
let room = Rect::from((pos, Extent2::from(sz))); let area = Rect::from((pos, Extent2::from(sz)));
let room_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space let area_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space
// Ensure no overlap // Ensure no overlap
if self.rooms if self.rooms
.iter() .iter()
.any(|r| r.collides_with_rect(room_border) || r.contains_point(self.stair_tile)) .any(|r| r.area.collides_with_rect(area_border) || r.area.contains_point(self.stair_tile))
{ {
return None; return None;
} }
Some(room) Some(area)
}) { }) {
Some(room) => room, Some(room) => room,
None => return, None => return,
}; };
self.rooms.push(room); let room = self.rooms.insert(Room {
seed: ctx.rng.gen(),
enemies: ctx.rng.gen(),
loot_density: level as f32 * 0.00025,
area,
});
for x in 0..room.extent().w { for x in 0..area.extent().w {
for y in 0..room.extent().h { for y in 0..area.extent().h {
self.tiles.set(room.position() + Vec2::new(x, y), Tile::Room); self.tiles.set(area.position() + Vec2::new(x, y), Tile::Room(room));
} }
} }
} }
@ -273,7 +286,7 @@ impl Floor {
.filter(|pos| self.tiles.get(*pos).is_some()) .filter(|pos| self.tiles.get(*pos).is_some())
}; };
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| match self.tiles.get(*b) { let transition = |a: &Vec2<i32>, b: &Vec2<i32>| match self.tiles.get(*b) {
Some(Tile::Room) | Some(Tile::Tunnel) => 1.0, Some(Tile::Room(_)) | Some(Tile::Tunnel) => 1.0,
Some(Tile::Solid) => 25.0, Some(Tile::Solid) => 25.0,
Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0, Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0,
_ => 100000.0, _ => 100000.0,
@ -306,12 +319,14 @@ impl Floor {
for x in aligned_area.min.x..aligned_area.max.x { for x in aligned_area.min.x..aligned_area.max.x {
for y in aligned_area.min.y..aligned_area.max.y { for y in aligned_area.min.y..aligned_area.max.y {
let tile_pos = Vec2::new(x, y); let tile_pos = Vec2::new(x, y);
if let Some(Tile::Room) = self.tiles.get(tile_pos) { if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
if tile_pos.x % 4 != 0 || tile_pos.y % 4 != 0 { continue; } // This is so bad let room = &self.rooms[*room];
supplement.add_entity(EntityInfo { if room.enemies && tile_pos.x % 4 == 0 && tile_pos.y % 4 == 0 { // Bad
pos: (origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32), let entity = EntityInfo::at((origin + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + TILE_SIZE / 2).map(|e| e as f32))
kind: EntityKind::Boss, .into_giant()
}); .with_alignment(Alignment::Enemy);
supplement.add_entity(entity);
}
} }
} }
} }
@ -372,8 +387,15 @@ impl Floor {
BlockMask::nothing() BlockMask::nothing()
} }
}, },
Some(Tile::Room) | Some(Tile::DownStair) if z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => BlockMask::nothing(), Some(Tile::Room(_)) | Some(Tile::DownStair) if z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => BlockMask::nothing(),
Some(Tile::Room) => empty, Some(Tile::Room(room)) => {
let room = &self.rooms[*room];
if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density) {
BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1)
} else {
empty
}
},
Some(Tile::DownStair) => make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0).resolve_with(empty), Some(Tile::DownStair) => make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0).resolve_with(empty),
Some(Tile::UpStair) => { Some(Tile::UpStair) => {
let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 0.5, 9.0); let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 0.5, 9.0);

View File

@ -8,6 +8,10 @@ pub struct RandomField {
impl RandomField { impl RandomField {
pub const fn new(seed: u32) -> Self { Self { seed } } pub const fn new(seed: u32) -> Self { Self { seed } }
pub fn chance(&self, pos: Vec3<i32>, chance: f32) -> bool {
(self.get(pos) % (1 << 10)) as f32 / ((1 << 10) as f32) < chance
}
} }
impl Sampler<'static> for RandomField { impl Sampler<'static> for RandomField {