mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Fixed figure AO (still need to do sprite AO), added more control over NPC spawning, loot in dungeons
This commit is contained in:
parent
3042a09dfd
commit
e3ebdc56b3
@ -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) {
|
||||
|
@ -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 *
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user