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_col;
in float f_ao;
flat in vec3 f_norm;
layout (std140)
@ -39,6 +40,12 @@ void main() {
vec3 point_light = light_at(f_pos, f_norm);
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);
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
@ -48,7 +55,7 @@ void main() {
if ((flags & 1) == 1 && int(cam_mode) == 1) {
float distance = distance(vec3(cam_pos), vec3(model_mat * vec4(vec3(0), 1))) - 2;
float opacity = clamp(distance / distance_divider, 0, 1);
if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > opacity) {

View File

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

View File

@ -19,6 +19,10 @@ out vec4 tgt_color;
#include <sky.glsl>
#include <light.glsl>
float vmin(vec3 v) {
return min(v.x, min(v.y, v.z));
}
void main() {
// 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));
@ -30,7 +34,7 @@ void main() {
// Use an array to avoid conditional branching
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;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);

View File

@ -1,14 +1,44 @@
use vek::*;
pub enum EntityKind {
Enemy,
Boss,
Waypoint,
}
use crate::comp::{Alignment};
pub struct EntityInfo {
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)]

View File

@ -1,6 +1,7 @@
use std::{
fmt,
hash,
ops::{Index, IndexMut},
cmp::{PartialEq, Eq},
marker::PhantomData,
};
@ -65,3 +66,12 @@ impl<T> Store<T> {
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,
comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos},
event::{EventBus, ServerEvent},
generation::EntityKind,
msg::ServerMsg,
npc::{self, NPC_NAMES},
state::TerrainChanges,
@ -101,295 +100,297 @@ impl<'a> System<'a> for Sys {
// Handle chunk supplement
for entity in supplement.entities {
if let EntityKind::Waypoint = entity.kind {
if entity.is_waypoint {
server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos));
} else {
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
}
continue;
}
const SPAWN_NPCS: &'static [fn() -> (
String,
comp::Body,
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);
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
}
let active_item =
if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) {
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
const SPAWN_NPCS: &'static [fn() -> (
String,
comp::Body,
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 {
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 alignment = entity.alignment;
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,
},
let active_item =
if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) {
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
main.map(|item| comp::ItemConfig {
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 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
// species instead
stats.level.set_level(rand::thread_rng().gen_range(1, 9));
let mut scale = 1.0;
// Replace stuff if it's a boss
if let EntityKind::Boss = entity.kind {
if rand::random::<f32>() < 0.65 {
let body_new = comp::humanoid::Body::random();
body = comp::Body::Humanoid(body_new);
alignment = comp::Alignment::Npc;
stats = comp::Stats::new(
format!(
"Fearless Giant {}",
get_npc_name(&NPC_NAMES.humanoid, body_new.race)
),
body,
);
}
loadout = comp::Loadout {
active_item: Some(comp::ItemConfig {
item: assets::load_expect_cloned(
"common.items.weapons.zweihander_sword_0",
),
ability1: Some(CharacterAbility::BasicMelee {
energy_cost: 0,
buildup_duration: Duration::from_millis(800),
recover_duration: Duration::from_millis(200),
base_healthchange: -13,
range: 3.5,
max_angle: 60.0,
}),
ability2: None,
ability3: None,
block_ability: None,
dodge_ability: None,
// TODO: Remove this and implement scaling or level depending on stuff like
// species instead
stats.level.set_level(rand::thread_rng().gen_range(1, 9));
// Replace stuff if it's a boss
if entity.is_giant {
if rand::random::<f32>() < 0.65 {
let body_new = comp::humanoid::Body::random();
body = comp::Body::Humanoid(body_new);
stats = comp::Stats::new(
format!(
"Fearless Giant {}",
get_npc_name(&NPC_NAMES.humanoid, body_new.race)
),
body,
);
}
loadout = comp::Loadout {
active_item: Some(comp::ItemConfig {
item: assets::load_expect_cloned(
"common.items.weapons.zweihander_sword_0",
),
ability1: Some(CharacterAbility::BasicMelee {
energy_cost: 0,
buildup_duration: Duration::from_millis(800),
recover_duration: Duration::from_millis(200),
base_healthchange: -13,
range: 3.5,
max_angle: 60.0,
}),
second_item: None,
shoulder: Some(assets::load_expect_cloned(
"common.items.armor.shoulder.plate_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,
};
ability2: None,
ability3: None,
block_ability: None,
dodge_ability: None,
}),
second_item: None,
shoulder: Some(assets::load_expect_cloned(
"common.items.armor.shoulder.plate_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,
};
stats.level.set_level(rand::thread_rng().gen_range(30, 35));
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.level.set_level(rand::thread_rng().gen_range(30, 35));
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),
})
}
}

View File

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

View File

@ -14,6 +14,7 @@ gfx_defines! {
pos: [f32; 3] = "v_pos",
norm: [f32; 3] = "v_norm",
col: [f32; 3] = "v_col",
ao: f32 = "v_ao",
bone_idx: u8 = "v_bone_idx",
}
@ -44,11 +45,12 @@ gfx_defines! {
}
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 {
pos: pos.into_array(),
col: col.into_array(),
norm: norm.into_array(),
ao,
bone_idx,
}
}

View File

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

View File

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

View File

@ -8,6 +8,10 @@ pub struct RandomField {
impl RandomField {
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 {