mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'scott-c/particles' into 'master'
Particles See merge request veloren/veloren!1156
This commit is contained in:
commit
893da3622a
@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Power stat to weapons which affects weapon damage
|
- Power stat to weapons which affects weapon damage
|
||||||
- Add detection of entities under the cursor
|
- Add detection of entities under the cursor
|
||||||
- Functional group-system with exp-sharing and disabled damage to group members
|
- Functional group-system with exp-sharing and disabled damage to group members
|
||||||
|
- Some Campfire, fireball & bomb; particle, light & sound effects.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -106,6 +106,18 @@
|
|||||||
"voxygen.audio.sfx.inventory.consumable.food",
|
"voxygen.audio.sfx.inventory.consumable.food",
|
||||||
],
|
],
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
)
|
),
|
||||||
|
Explosion: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.explosion",
|
||||||
|
],
|
||||||
|
threshold: 0.2,
|
||||||
|
),
|
||||||
|
ProjectileShot: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.glider_open",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
BIN
assets/voxygen/audio/sfx/explosion.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/explosion.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -297,6 +297,7 @@ magically infused items?"#,
|
|||||||
"hud.settings.fluid_rendering_mode.cheap": "Cheap",
|
"hud.settings.fluid_rendering_mode.cheap": "Cheap",
|
||||||
"hud.settings.fluid_rendering_mode.shiny": "Shiny",
|
"hud.settings.fluid_rendering_mode.shiny": "Shiny",
|
||||||
"hud.settings.cloud_rendering_mode.regular": "Regular",
|
"hud.settings.cloud_rendering_mode.regular": "Regular",
|
||||||
|
"hud.settings.particles": "Particles",
|
||||||
"hud.settings.fullscreen": "Fullscreen",
|
"hud.settings.fullscreen": "Fullscreen",
|
||||||
"hud.settings.save_window_size": "Save window size",
|
"hud.settings.save_window_size": "Save window size",
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
float hash(vec4 p) {
|
float hash(vec4 p) {
|
||||||
p = fract(p * 0.3183099 + 0.1);
|
p = fract(p * 0.3183099 + 0.1) - fract(p + 23.22121);
|
||||||
p *= 17.0;
|
p *= 17.0;
|
||||||
return (fract(p.x * p.y * p.z * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0;
|
return (fract(p.x * p.y * p.z * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0;
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,10 @@ float is_star_at(vec3 dir) {
|
|||||||
vec3 pos = (floor(dir * star_scale) - 0.5) / star_scale;
|
vec3 pos = (floor(dir * star_scale) - 0.5) / star_scale;
|
||||||
|
|
||||||
// Noisy offsets
|
// Noisy offsets
|
||||||
pos += (3.0 / star_scale) * rand_perm_3(pos);
|
pos += (3.0 / star_scale) * (1.0 + hash(pos.yxzz) * 0.85);
|
||||||
|
|
||||||
// Find distance to fragment
|
// Find distance to fragment
|
||||||
float dist = length(normalize(pos) - dir);
|
float dist = length(pos - dir);
|
||||||
|
|
||||||
// Star threshold
|
// Star threshold
|
||||||
if (dist < 0.0015) {
|
if (dist < 0.0015) {
|
||||||
|
38
assets/voxygen/shaders/particle-frag.glsl
Normal file
38
assets/voxygen/shaders/particle-frag.glsl
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
#include <globals.glsl>
|
||||||
|
|
||||||
|
in vec3 f_pos;
|
||||||
|
flat in vec3 f_norm;
|
||||||
|
in vec3 f_col;
|
||||||
|
in float f_ao;
|
||||||
|
in float f_light;
|
||||||
|
|
||||||
|
out vec4 tgt_color;
|
||||||
|
|
||||||
|
#include <sky.glsl>
|
||||||
|
#include <light.glsl>
|
||||||
|
|
||||||
|
const float FADE_DIST = 32.0;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 light, diffuse_light, ambient_light;
|
||||||
|
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);
|
||||||
|
float point_shadow = shadow_at(f_pos, f_norm);
|
||||||
|
diffuse_light *= f_light * point_shadow;
|
||||||
|
ambient_light *= f_light, point_shadow;
|
||||||
|
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(f_col, light, diffuse_light, ambient_light);
|
||||||
|
|
||||||
|
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
|
||||||
|
vec4 clouds;
|
||||||
|
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, cam_pos.xyz, f_pos, 0.5, true, clouds);
|
||||||
|
vec3 color = mix(mix(surf_color, fog_color, fog_level), clouds.rgb, clouds.a);
|
||||||
|
|
||||||
|
tgt_color = vec4(color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (1000.0 - FADE_DIST)) / FADE_DIST, 0, 1));
|
||||||
|
}
|
131
assets/voxygen/shaders/particle-vert.glsl
Normal file
131
assets/voxygen/shaders/particle-vert.glsl
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
#include <globals.glsl>
|
||||||
|
#include <srgb.glsl>
|
||||||
|
#include <random.glsl>
|
||||||
|
|
||||||
|
in vec3 v_pos;
|
||||||
|
in uint v_col;
|
||||||
|
in uint v_norm_ao;
|
||||||
|
in vec3 inst_pos;
|
||||||
|
in float inst_time;
|
||||||
|
in float inst_entropy;
|
||||||
|
in int inst_mode;
|
||||||
|
|
||||||
|
out vec3 f_pos;
|
||||||
|
flat out vec3 f_norm;
|
||||||
|
out vec3 f_col;
|
||||||
|
out float f_ao;
|
||||||
|
out float f_light;
|
||||||
|
|
||||||
|
const float SCALE = 1.0 / 11.0;
|
||||||
|
|
||||||
|
// Modes
|
||||||
|
const int SMOKE = 0;
|
||||||
|
const int FIRE = 1;
|
||||||
|
const int GUN_POWDER_SPARK = 2;
|
||||||
|
const int SHRAPNEL = 3;
|
||||||
|
|
||||||
|
// meters per second squared (acceleration)
|
||||||
|
const float earth_gravity = 9.807;
|
||||||
|
|
||||||
|
struct Attr {
|
||||||
|
vec3 offs;
|
||||||
|
float scale;
|
||||||
|
vec3 col;
|
||||||
|
};
|
||||||
|
|
||||||
|
float lifetime = tick.x - inst_time;
|
||||||
|
|
||||||
|
vec3 linear_motion(vec3 init_offs, vec3 vel) {
|
||||||
|
return init_offs + vel * lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 grav_vel(float grav) {
|
||||||
|
return vec3(0, 0, -grav * lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
float exp_scale(float factor) {
|
||||||
|
return 1 / (1 - lifetime * factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float rand0 = hash(vec4(inst_entropy + 0));
|
||||||
|
float rand1 = hash(vec4(inst_entropy + 1));
|
||||||
|
float rand2 = hash(vec4(inst_entropy + 2));
|
||||||
|
float rand3 = hash(vec4(inst_entropy + 3));
|
||||||
|
float rand4 = hash(vec4(inst_entropy + 4));
|
||||||
|
float rand5 = hash(vec4(inst_entropy + 5));
|
||||||
|
float rand6 = hash(vec4(inst_entropy + 6));
|
||||||
|
float rand7 = hash(vec4(inst_entropy + 7));
|
||||||
|
|
||||||
|
Attr attr;
|
||||||
|
|
||||||
|
if (inst_mode == SMOKE) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
|
||||||
|
vec3(rand2 * 0.2, rand3 * 0.2, 1.0 + rand4 * 0.5)// + vec3(sin(lifetime), sin(lifetime + 1.5), sin(lifetime * 4) * 0.25)
|
||||||
|
),
|
||||||
|
exp_scale(-0.2),
|
||||||
|
vec3(1)
|
||||||
|
);
|
||||||
|
} else if (inst_mode == FIRE) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0 * 0.25, rand1 * 0.25, 0.3),
|
||||||
|
vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0)
|
||||||
|
),
|
||||||
|
1.0,
|
||||||
|
vec3(2, rand5 + 2, 0)
|
||||||
|
);
|
||||||
|
} else if (inst_mode == GUN_POWDER_SPARK) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0, rand1, rand3) * 0.3,
|
||||||
|
vec3(rand4, rand5, rand6) * 2.0 + grav_vel(earth_gravity)
|
||||||
|
),
|
||||||
|
1.0,
|
||||||
|
vec3(3.5, 3 + rand7, 0)
|
||||||
|
);
|
||||||
|
} else if (inst_mode == SHRAPNEL) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(0),
|
||||||
|
vec3(rand4, rand5, rand6) * 40.0 + grav_vel(earth_gravity)
|
||||||
|
),
|
||||||
|
3.0 + rand0,
|
||||||
|
vec3(0.6 + rand7 * 0.4)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
|
||||||
|
vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5)
|
||||||
|
),
|
||||||
|
exp_scale(-0.2),
|
||||||
|
vec3(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
f_pos = inst_pos + (v_pos * attr.scale * SCALE + attr.offs);
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
f_norm =
|
||||||
|
// inst_pos *
|
||||||
|
normals[(v_norm_ao >> 0) & 0x7u];
|
||||||
|
|
||||||
|
//vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0;
|
||||||
|
f_col =
|
||||||
|
//srgb_to_linear(col) *
|
||||||
|
srgb_to_linear(attr.col);
|
||||||
|
f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0;
|
||||||
|
|
||||||
|
f_light = 1.0;
|
||||||
|
|
||||||
|
gl_Position =
|
||||||
|
all_mat *
|
||||||
|
vec4(f_pos, 1);
|
||||||
|
gl_Position.z = -1000.0 / (gl_Position.z + 10000.0);
|
||||||
|
}
|
BIN
assets/voxygen/voxel/particle.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/particle.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -25,6 +25,7 @@ use common::{
|
|||||||
Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo,
|
Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo,
|
||||||
ServerMsg, MAX_BYTES_CHAT_MSG,
|
ServerMsg, MAX_BYTES_CHAT_MSG,
|
||||||
},
|
},
|
||||||
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
state::State,
|
state::State,
|
||||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||||
@ -66,6 +67,7 @@ pub enum Event {
|
|||||||
InventoryUpdated(InventoryUpdateEvent),
|
InventoryUpdated(InventoryUpdateEvent),
|
||||||
Notification(Notification),
|
Notification(Notification),
|
||||||
SetViewDistance(u32),
|
SetViewDistance(u32),
|
||||||
|
Outcome(Outcome),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
@ -1244,6 +1246,9 @@ impl Client {
|
|||||||
self.view_distance = Some(vd);
|
self.view_distance = Some(vd);
|
||||||
frontend_events.push(Event::SetViewDistance(vd));
|
frontend_events.push(Event::SetViewDistance(vd));
|
||||||
},
|
},
|
||||||
|
ServerMsg::Outcomes(outcomes) => {
|
||||||
|
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ pub enum ChatCommand {
|
|||||||
Adminify,
|
Adminify,
|
||||||
Alias,
|
Alias,
|
||||||
Build,
|
Build,
|
||||||
|
Campfire,
|
||||||
Debug,
|
Debug,
|
||||||
DebugColumn,
|
DebugColumn,
|
||||||
Dummy,
|
Dummy,
|
||||||
@ -79,6 +80,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
|||||||
ChatCommand::Adminify,
|
ChatCommand::Adminify,
|
||||||
ChatCommand::Alias,
|
ChatCommand::Alias,
|
||||||
ChatCommand::Build,
|
ChatCommand::Build,
|
||||||
|
ChatCommand::Campfire,
|
||||||
ChatCommand::Debug,
|
ChatCommand::Debug,
|
||||||
ChatCommand::DebugColumn,
|
ChatCommand::DebugColumn,
|
||||||
ChatCommand::Dummy,
|
ChatCommand::Dummy,
|
||||||
@ -185,6 +187,7 @@ impl ChatCommand {
|
|||||||
),
|
),
|
||||||
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin),
|
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin),
|
||||||
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin),
|
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin),
|
||||||
|
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Admin),
|
||||||
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
|
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
|
||||||
ChatCommand::DebugColumn => cmd(
|
ChatCommand::DebugColumn => cmd(
|
||||||
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
||||||
@ -365,6 +368,7 @@ impl ChatCommand {
|
|||||||
ChatCommand::Adminify => "adminify",
|
ChatCommand::Adminify => "adminify",
|
||||||
ChatCommand::Alias => "alias",
|
ChatCommand::Alias => "alias",
|
||||||
ChatCommand::Build => "build",
|
ChatCommand::Build => "build",
|
||||||
|
ChatCommand::Campfire => "campfire",
|
||||||
ChatCommand::Debug => "debug",
|
ChatCommand::Debug => "debug",
|
||||||
ChatCommand::DebugColumn => "debug_column",
|
ChatCommand::DebugColumn => "debug_column",
|
||||||
ChatCommand::Dummy => "dummy",
|
ChatCommand::Dummy => "dummy",
|
||||||
|
@ -261,6 +261,7 @@ impl Tool {
|
|||||||
col: (0.85, 0.5, 0.11).into(),
|
col: (0.85, 0.5, 0.11).into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
projectile_gravity: None,
|
projectile_gravity: None,
|
||||||
},
|
},
|
||||||
BasicRanged {
|
BasicRanged {
|
||||||
|
@ -18,7 +18,7 @@ mod player;
|
|||||||
pub mod projectile;
|
pub mod projectile;
|
||||||
pub mod skills;
|
pub mod skills;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod visual;
|
pub mod visual;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
|
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
|
||||||
|
@ -24,6 +24,10 @@ impl Component for Vel {
|
|||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Ori(pub Dir);
|
pub struct Ori(pub Dir);
|
||||||
|
|
||||||
|
impl Ori {
|
||||||
|
pub fn vec(&self) -> &Vec3<f32> { &*self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for Ori {
|
impl Component for Ori {
|
||||||
type Storage = IdvStorage<Self>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ pub mod generation;
|
|||||||
pub mod loadout_builder;
|
pub mod loadout_builder;
|
||||||
pub mod msg;
|
pub mod msg;
|
||||||
pub mod npc;
|
pub mod npc;
|
||||||
|
pub mod outcome;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
pub mod ray;
|
pub mod ray;
|
||||||
pub mod recipe;
|
pub mod recipe;
|
||||||
|
@ -2,6 +2,7 @@ use super::{ClientState, EcsCompPacket};
|
|||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterItem,
|
character::CharacterItem,
|
||||||
comp,
|
comp,
|
||||||
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
state, sync,
|
state, sync,
|
||||||
sync::Uid,
|
sync::Uid,
|
||||||
@ -120,6 +121,7 @@ pub enum ServerMsg {
|
|||||||
/// Send a popup notification such as "Waypoint Saved"
|
/// Send a popup notification such as "Waypoint Saved"
|
||||||
Notification(Notification),
|
Notification(Notification),
|
||||||
SetViewDistance(u32),
|
SetViewDistance(u32),
|
||||||
|
Outcomes(Vec<Outcome>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
30
common/src/outcome.rs
Normal file
30
common/src/outcome.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::comp;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
/// An outcome represents the final result of an instantaneous event. It implies
|
||||||
|
/// that said event has already occurred. It is not a request for that event to
|
||||||
|
/// occur, nor is it something that may be cancelled or otherwise altered. Its
|
||||||
|
/// primary purpose is to act as something for frontends (both server and
|
||||||
|
/// client) to listen to in order to receive feedback about events in the world.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum Outcome {
|
||||||
|
Explosion {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
power: f32,
|
||||||
|
},
|
||||||
|
ProjectileShot {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
body: comp::Body,
|
||||||
|
vel: Vec3<f32>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Outcome {
|
||||||
|
pub fn get_pos(&self) -> Option<Vec3<f32>> {
|
||||||
|
match self {
|
||||||
|
Outcome::Explosion { pos, .. } => Some(*pos),
|
||||||
|
Outcome::ProjectileShot { pos, .. } => Some(*pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ use chrono::{NaiveTime, Timelike};
|
|||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
||||||
comp::{self, ChatType, Item},
|
comp::{self, ChatType, Item, LightEmitter, WaypointArea},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
msg::{Notification, PlayerListUpdate, ServerMsg},
|
msg::{Notification, PlayerListUpdate, ServerMsg},
|
||||||
npc::{self, get_npc_name},
|
npc::{self, get_npc_name},
|
||||||
@ -65,6 +65,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
|||||||
ChatCommand::Adminify => handle_adminify,
|
ChatCommand::Adminify => handle_adminify,
|
||||||
ChatCommand::Alias => handle_alias,
|
ChatCommand::Alias => handle_alias,
|
||||||
ChatCommand::Build => handle_build,
|
ChatCommand::Build => handle_build,
|
||||||
|
ChatCommand::Campfire => handle_spawn_campfire,
|
||||||
ChatCommand::Debug => handle_debug,
|
ChatCommand::Debug => handle_debug,
|
||||||
ChatCommand::DebugColumn => handle_debug_column,
|
ChatCommand::DebugColumn => handle_debug_column,
|
||||||
ChatCommand::Dummy => handle_spawn_training_dummy,
|
ChatCommand::Dummy => handle_spawn_training_dummy,
|
||||||
@ -664,6 +665,39 @@ fn handle_spawn_training_dummy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_spawn_campfire(
|
||||||
|
server: &mut Server,
|
||||||
|
client: EcsEntity,
|
||||||
|
target: EcsEntity,
|
||||||
|
_args: String,
|
||||||
|
_action: &ChatCommand,
|
||||||
|
) {
|
||||||
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
|
Some(pos) => {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.create_object(pos, comp::object::Body::CampfireLit)
|
||||||
|
.with(LightEmitter {
|
||||||
|
col: Rgb::new(1.0, 0.65, 0.2),
|
||||||
|
strength: 2.0,
|
||||||
|
flicker: 1.0,
|
||||||
|
animated: true,
|
||||||
|
})
|
||||||
|
.with(WaypointArea::default())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ChatType::CommandInfo.server_msg("Spawned a campfire"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
None => server.notify_client(
|
||||||
|
client,
|
||||||
|
ChatType::CommandError.server_msg("You have no position!"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_players(
|
fn handle_players(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use crate::{sys, Server, StateExt};
|
use crate::{sys, Server, StateExt};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self, group, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
|
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
|
||||||
Projectile, Scale, Stats, Vel, WaypointArea,
|
Projectile, Scale, Stats, Vel, WaypointArea,
|
||||||
},
|
},
|
||||||
|
outcome::Outcome,
|
||||||
util::Dir,
|
util::Dir,
|
||||||
};
|
};
|
||||||
|
use comp::group;
|
||||||
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
||||||
use vek::{Rgb, Vec3};
|
use vek::{Rgb, Vec3};
|
||||||
|
|
||||||
@ -89,10 +91,18 @@ pub fn handle_shoot(
|
|||||||
.expect("Failed to fetch entity")
|
.expect("Failed to fetch entity")
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
|
let vel = *dir * 100.0;
|
||||||
|
|
||||||
|
// Add an outcome
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<Vec<Outcome>>()
|
||||||
|
.push(Outcome::ProjectileShot { pos, body, vel });
|
||||||
|
|
||||||
// TODO: Player height
|
// TODO: Player height
|
||||||
pos.z += 1.2;
|
pos.z += 1.2;
|
||||||
|
|
||||||
let mut builder = state.create_projectile(Pos(pos), Vel(*dir * 100.0), body, projectile);
|
let mut builder = state.create_projectile(Pos(pos), Vel(vel), body, projectile);
|
||||||
if let Some(light) = light {
|
if let Some(light) = light {
|
||||||
builder = builder.with(light)
|
builder = builder.with(light)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use common::{
|
|||||||
HealthChange, HealthSource, Player, Pos, Stats,
|
HealthChange, HealthSource, Player, Pos, Stats,
|
||||||
},
|
},
|
||||||
msg::{PlayerListUpdate, ServerMsg},
|
msg::{PlayerListUpdate, ServerMsg},
|
||||||
|
outcome::Outcome,
|
||||||
state::BlockChange,
|
state::BlockChange,
|
||||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||||
sys::combat::BLOCK_ANGLE,
|
sys::combat::BLOCK_ANGLE,
|
||||||
@ -288,6 +289,10 @@ pub fn handle_explosion(
|
|||||||
let hit_range = 3.0 * power;
|
let hit_range = 3.0 * power;
|
||||||
let ecs = &server.state.ecs();
|
let ecs = &server.state.ecs();
|
||||||
|
|
||||||
|
// Add an outcome
|
||||||
|
ecs.write_resource::<Vec<Outcome>>()
|
||||||
|
.push(Outcome::Explosion { pos, power });
|
||||||
|
|
||||||
let owner_entity = owner.and_then(|uid| {
|
let owner_entity = owner.and_then(|uid| {
|
||||||
ecs.read_resource::<UidAllocator>()
|
ecs.read_resource::<UidAllocator>()
|
||||||
.retrieve_entity_internal(uid.into())
|
.retrieve_entity_internal(uid.into())
|
||||||
|
@ -34,6 +34,7 @@ use common::{
|
|||||||
comp::{self, ChatType},
|
comp::{self, ChatType},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
msg::{ClientState, ServerInfo, ServerMsg},
|
msg::{ClientState, ServerInfo, ServerMsg},
|
||||||
|
outcome::Outcome,
|
||||||
recipe::default_recipe_book,
|
recipe::default_recipe_book,
|
||||||
state::{State, TimeOfDay},
|
state::{State, TimeOfDay},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
@ -118,6 +119,7 @@ impl Server {
|
|||||||
state
|
state
|
||||||
.ecs_mut()
|
.ecs_mut()
|
||||||
.insert(comp::AdminList(settings.admins.clone()));
|
.insert(comp::AdminList(settings.admins.clone()));
|
||||||
|
state.ecs_mut().insert(Vec::<Outcome>::new());
|
||||||
|
|
||||||
// System timers for performance monitoring
|
// System timers for performance monitoring
|
||||||
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
||||||
|
@ -7,15 +7,19 @@ use crate::{
|
|||||||
Tick,
|
Tick,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel},
|
comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
|
||||||
msg::ServerMsg,
|
msg::ServerMsg,
|
||||||
|
outcome::Outcome,
|
||||||
region::{Event as RegionEvent, RegionMap},
|
region::{Event as RegionEvent, RegionMap},
|
||||||
state::TimeOfDay,
|
state::TimeOfDay,
|
||||||
sync::{CompSyncPackage, Uid},
|
sync::{CompSyncPackage, Uid},
|
||||||
|
terrain::TerrainChunkSize,
|
||||||
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use specs::{
|
use specs::{
|
||||||
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage,
|
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage,
|
||||||
};
|
};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
/// This system will send physics updates to the client
|
/// This system will send physics updates to the client
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
@ -33,6 +37,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Ori>,
|
ReadStorage<'a, Ori>,
|
||||||
ReadStorage<'a, Inventory>,
|
ReadStorage<'a, Inventory>,
|
||||||
ReadStorage<'a, RegionSubscription>,
|
ReadStorage<'a, RegionSubscription>,
|
||||||
|
ReadStorage<'a, Player>,
|
||||||
WriteStorage<'a, Last<Pos>>,
|
WriteStorage<'a, Last<Pos>>,
|
||||||
WriteStorage<'a, Last<Vel>>,
|
WriteStorage<'a, Last<Vel>>,
|
||||||
WriteStorage<'a, Last<Ori>>,
|
WriteStorage<'a, Last<Ori>>,
|
||||||
@ -40,6 +45,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, ForceUpdate>,
|
WriteStorage<'a, ForceUpdate>,
|
||||||
WriteStorage<'a, InventoryUpdate>,
|
WriteStorage<'a, InventoryUpdate>,
|
||||||
Write<'a, DeletedEntities>,
|
Write<'a, DeletedEntities>,
|
||||||
|
Write<'a, Vec<Outcome>>,
|
||||||
TrackedComps<'a>,
|
TrackedComps<'a>,
|
||||||
ReadTrackers<'a>,
|
ReadTrackers<'a>,
|
||||||
);
|
);
|
||||||
@ -58,6 +64,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
orientations,
|
orientations,
|
||||||
inventories,
|
inventories,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
|
players,
|
||||||
mut last_pos,
|
mut last_pos,
|
||||||
mut last_vel,
|
mut last_vel,
|
||||||
mut last_ori,
|
mut last_ori,
|
||||||
@ -65,6 +72,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut force_updates,
|
mut force_updates,
|
||||||
mut inventory_updates,
|
mut inventory_updates,
|
||||||
mut deleted_entities,
|
mut deleted_entities,
|
||||||
|
mut outcomes,
|
||||||
tracked_comps,
|
tracked_comps,
|
||||||
trackers,
|
trackers,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
@ -316,6 +324,26 @@ impl<'a> System<'a> for Sys {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync outcomes
|
||||||
|
for (client, player, pos) in (&mut clients, &players, positions.maybe()).join() {
|
||||||
|
let is_near = |o_pos: Vec3<f32>| {
|
||||||
|
pos.zip_with(player.view_distance, |pos, vd| {
|
||||||
|
pos.0.xy().distance_squared(o_pos.xy())
|
||||||
|
< (vd as f32 * TerrainChunkSize::RECT_SIZE.x as f32).powf(2.0)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let outcomes = outcomes
|
||||||
|
.iter()
|
||||||
|
.filter(|o| o.get_pos().and_then(&is_near).unwrap_or(true))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if outcomes.len() > 0 {
|
||||||
|
client.notify(ServerMsg::Outcomes(outcomes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outcomes.clear();
|
||||||
|
|
||||||
// Remove all force flags.
|
// Remove all force flags.
|
||||||
force_updates.clear();
|
force_updates.clear();
|
||||||
inventory_updates.clear();
|
inventory_updates.clear();
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
//! the channel capacity has been reached and all channels are occupied, a
|
//! the channel capacity has been reached and all channels are occupied, a
|
||||||
//! warning is logged, and no sound is played.
|
//! warning is logged, and no sound is played.
|
||||||
|
|
||||||
use crate::audio::fader::{FadeDirection, Fader};
|
use crate::audio::{
|
||||||
|
fader::{FadeDirection, Fader},
|
||||||
|
Listener,
|
||||||
|
};
|
||||||
use rodio::{Device, Sample, Sink, Source, SpatialSink};
|
use rodio::{Device, Sample, Sink, Source, SpatialSink};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -163,11 +166,16 @@ impl SfxChannel {
|
|||||||
|
|
||||||
pub fn is_done(&self) -> bool { self.sink.empty() }
|
pub fn is_done(&self) -> bool { self.sink.empty() }
|
||||||
|
|
||||||
pub fn set_emitter_position(&mut self, pos: [f32; 3]) { self.sink.set_emitter_position(pos); }
|
pub fn set_pos(&mut self, pos: Vec3<f32>) { self.pos = pos; }
|
||||||
|
|
||||||
pub fn set_left_ear_position(&mut self, pos: [f32; 3]) { self.sink.set_left_ear_position(pos); }
|
pub fn update(&mut self, listener: &Listener) {
|
||||||
|
const FALLOFF: f32 = 0.13;
|
||||||
|
|
||||||
pub fn set_right_ear_position(&mut self, pos: [f32; 3]) {
|
self.sink
|
||||||
self.sink.set_right_ear_position(pos);
|
.set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array());
|
||||||
|
self.sink
|
||||||
|
.set_left_ear_position(listener.ear_left_rpos.into_array());
|
||||||
|
self.sink
|
||||||
|
.set_right_ear_position(listener.ear_right_rpos.into_array());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,14 @@ use cpal::traits::DeviceTrait;
|
|||||||
use rodio::{source::Source, Decoder, Device};
|
use rodio::{source::Source, Decoder, Device};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
const FALLOFF: f32 = 0.13;
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Listener {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
ori: Vec3<f32>,
|
||||||
|
|
||||||
|
ear_left_rpos: Vec3<f32>,
|
||||||
|
ear_right_rpos: Vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds information about the system audio devices and internal channels used
|
/// Holds information about the system audio devices and internal channels used
|
||||||
/// for sfx and music playback. An instance of `AudioFrontend` is used by
|
/// for sfx and music playback. An instance of `AudioFrontend` is used by
|
||||||
@ -34,11 +41,7 @@ pub struct AudioFrontend {
|
|||||||
sfx_volume: f32,
|
sfx_volume: f32,
|
||||||
music_volume: f32,
|
music_volume: f32,
|
||||||
|
|
||||||
listener_pos: Vec3<f32>,
|
listener: Listener,
|
||||||
listener_ori: Vec3<f32>,
|
|
||||||
|
|
||||||
listener_ear_left: Vec3<f32>,
|
|
||||||
listener_ear_right: Vec3<f32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFrontend {
|
impl AudioFrontend {
|
||||||
@ -63,10 +66,8 @@ impl AudioFrontend {
|
|||||||
sfx_channels,
|
sfx_channels,
|
||||||
sfx_volume: 1.0,
|
sfx_volume: 1.0,
|
||||||
music_volume: 1.0,
|
music_volume: 1.0,
|
||||||
listener_pos: Vec3::zero(),
|
|
||||||
listener_ori: Vec3::zero(),
|
listener: Listener::default(),
|
||||||
listener_ear_left: Vec3::zero(),
|
|
||||||
listener_ear_right: Vec3::zero(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,10 +82,7 @@ impl AudioFrontend {
|
|||||||
sfx_channels: Vec::new(),
|
sfx_channels: Vec::new(),
|
||||||
sfx_volume: 1.0,
|
sfx_volume: 1.0,
|
||||||
music_volume: 1.0,
|
music_volume: 1.0,
|
||||||
listener_pos: Vec3::zero(),
|
listener: Listener::default(),
|
||||||
listener_ori: Vec3::zero(),
|
|
||||||
listener_ear_left: Vec3::zero(),
|
|
||||||
listener_ear_right: Vec3::zero(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,20 +144,15 @@ impl AudioFrontend {
|
|||||||
/// Play (once) an sfx file by file path at the give position and volume
|
/// Play (once) an sfx file by file path at the give position and volume
|
||||||
pub fn play_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
pub fn play_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
||||||
if self.audio_device.is_some() {
|
if self.audio_device.is_some() {
|
||||||
let calc_pos = ((pos - self.listener_pos) * FALLOFF).into_array();
|
|
||||||
|
|
||||||
let sound = self
|
let sound = self
|
||||||
.sound_cache
|
.sound_cache
|
||||||
.load_sound(sound)
|
.load_sound(sound)
|
||||||
.amplify(vol.unwrap_or(1.0));
|
.amplify(vol.unwrap_or(1.0));
|
||||||
|
|
||||||
let left_ear = self.listener_ear_left.into_array();
|
let listener = self.listener.clone();
|
||||||
let right_ear = self.listener_ear_right.into_array();
|
|
||||||
|
|
||||||
if let Some(channel) = self.get_sfx_channel() {
|
if let Some(channel) = self.get_sfx_channel() {
|
||||||
channel.set_emitter_position(calc_pos);
|
channel.set_pos(pos);
|
||||||
channel.set_left_ear_position(left_ear);
|
channel.update(&listener);
|
||||||
channel.set_right_ear_position(right_ear);
|
|
||||||
channel.play(sound);
|
channel.play(sound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,27 +167,17 @@ impl AudioFrontend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_listener_pos(&mut self, pos: &Vec3<f32>, ori: &Vec3<f32>) {
|
pub fn set_listener_pos(&mut self, pos: Vec3<f32>, ori: Vec3<f32>) {
|
||||||
self.listener_pos = *pos;
|
self.listener.pos = pos;
|
||||||
self.listener_ori = ori.normalized();
|
self.listener.ori = ori.normalized();
|
||||||
|
|
||||||
let up = Vec3::new(0.0, 0.0, 1.0);
|
let up = Vec3::new(0.0, 0.0, 1.0);
|
||||||
|
self.listener.ear_left_rpos = up.cross(self.listener.ori).normalized();
|
||||||
let pos_left = up.cross(self.listener_ori).normalized();
|
self.listener.ear_right_rpos = -up.cross(self.listener.ori).normalized();
|
||||||
let pos_right = self.listener_ori.cross(up).normalized();
|
|
||||||
|
|
||||||
self.listener_ear_left = pos_left;
|
|
||||||
self.listener_ear_right = pos_right;
|
|
||||||
|
|
||||||
for channel in self.sfx_channels.iter_mut() {
|
for channel in self.sfx_channels.iter_mut() {
|
||||||
if !channel.is_done() {
|
if !channel.is_done() {
|
||||||
// TODO: Update this to correctly determine the updated relative position of
|
channel.update(&self.listener);
|
||||||
// the SFX emitter when the player (listener) moves
|
|
||||||
// channel.set_emitter_position(
|
|
||||||
// ((channel.pos - self.listener_pos) * FALLOFF).into_array(),
|
|
||||||
// );
|
|
||||||
channel.set_left_ear_position(pos_left.into_array());
|
|
||||||
channel.set_right_ear_position(pos_right.into_array());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
/// EventMapper::Combat watches the combat states of surrounding entities' and
|
/// EventMapper::Combat watches the combat states of surrounding entities' and
|
||||||
/// emits sfx related to weapons and attacks/abilities
|
/// emits sfx related to weapons and attacks/abilities
|
||||||
use crate::audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR};
|
use crate::{
|
||||||
|
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||||
|
scene::Camera,
|
||||||
|
};
|
||||||
|
|
||||||
use super::EventMapper;
|
use super::EventMapper;
|
||||||
|
|
||||||
@ -15,7 +18,6 @@ use common::{
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use specs::{Entity as EcsEntity, Join, WorldExt};
|
use specs::{Entity as EcsEntity, Join, WorldExt};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use vek::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct PreviousEntityState {
|
struct PreviousEntityState {
|
||||||
@ -39,16 +41,19 @@ pub struct CombatEventMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventMapper for CombatEventMapper {
|
impl EventMapper for CombatEventMapper {
|
||||||
fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) {
|
fn maintain(
|
||||||
|
&mut self,
|
||||||
|
state: &State,
|
||||||
|
player_entity: specs::Entity,
|
||||||
|
camera: &Camera,
|
||||||
|
triggers: &SfxTriggers,
|
||||||
|
) {
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||||
let mut sfx_emitter = sfx_event_bus.emitter();
|
let mut sfx_emitter = sfx_event_bus.emitter();
|
||||||
|
|
||||||
let player_position = ecs
|
let cam_pos = camera.dependents().cam_pos;
|
||||||
.read_storage::<Pos>()
|
|
||||||
.get(player_entity)
|
|
||||||
.map_or(Vec3::zero(), |pos| pos.0);
|
|
||||||
|
|
||||||
for (entity, pos, loadout, character) in (
|
for (entity, pos, loadout, character) in (
|
||||||
&ecs.entities(),
|
&ecs.entities(),
|
||||||
@ -57,9 +62,7 @@ impl EventMapper for CombatEventMapper {
|
|||||||
ecs.read_storage::<CharacterState>().maybe(),
|
ecs.read_storage::<CharacterState>().maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(_, e_pos, ..)| {
|
.filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
|
||||||
(e_pos.0.distance_squared(player_position)) < SFX_DIST_LIMIT_SQR
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
if let Some(character) = character {
|
if let Some(character) = character {
|
||||||
let state = self.event_history.entry(entity).or_default();
|
let state = self.event_history.entry(entity).or_default();
|
||||||
|
@ -9,9 +9,16 @@ use movement::MovementEventMapper;
|
|||||||
use progression::ProgressionEventMapper;
|
use progression::ProgressionEventMapper;
|
||||||
|
|
||||||
use super::SfxTriggers;
|
use super::SfxTriggers;
|
||||||
|
use crate::scene::Camera;
|
||||||
|
|
||||||
trait EventMapper {
|
trait EventMapper {
|
||||||
fn maintain(&mut self, state: &State, player_entity: specs::Entity, triggers: &SfxTriggers);
|
fn maintain(
|
||||||
|
&mut self,
|
||||||
|
state: &State,
|
||||||
|
player_entity: specs::Entity,
|
||||||
|
camera: &Camera,
|
||||||
|
triggers: &SfxTriggers,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SfxEventMapper {
|
pub struct SfxEventMapper {
|
||||||
@ -33,10 +40,11 @@ impl SfxEventMapper {
|
|||||||
&mut self,
|
&mut self,
|
||||||
state: &State,
|
state: &State,
|
||||||
player_entity: specs::Entity,
|
player_entity: specs::Entity,
|
||||||
|
camera: &Camera,
|
||||||
triggers: &SfxTriggers,
|
triggers: &SfxTriggers,
|
||||||
) {
|
) {
|
||||||
for mapper in &mut self.mappers {
|
for mapper in &mut self.mappers {
|
||||||
mapper.maintain(state, player_entity, triggers);
|
mapper.maintain(state, player_entity, camera, triggers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
/// and triggers sfx related to running, climbing and gliding, at a volume
|
/// and triggers sfx related to running, climbing and gliding, at a volume
|
||||||
/// proportionate to the extity's size
|
/// proportionate to the extity's size
|
||||||
use super::EventMapper;
|
use super::EventMapper;
|
||||||
use crate::audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR};
|
use crate::{
|
||||||
|
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR},
|
||||||
|
scene::Camera,
|
||||||
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Body, CharacterState, PhysicsState, Pos, Vel},
|
comp::{Body, CharacterState, PhysicsState, Pos, Vel},
|
||||||
event::EventBus,
|
event::EventBus,
|
||||||
@ -35,16 +38,19 @@ pub struct MovementEventMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventMapper for MovementEventMapper {
|
impl EventMapper for MovementEventMapper {
|
||||||
fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) {
|
fn maintain(
|
||||||
|
&mut self,
|
||||||
|
state: &State,
|
||||||
|
player_entity: specs::Entity,
|
||||||
|
camera: &Camera,
|
||||||
|
triggers: &SfxTriggers,
|
||||||
|
) {
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
let sfx_event_bus = ecs.read_resource::<EventBus<SfxEventItem>>();
|
||||||
let mut sfx_emitter = sfx_event_bus.emitter();
|
let mut sfx_emitter = sfx_event_bus.emitter();
|
||||||
|
|
||||||
let player_position = ecs
|
let cam_pos = camera.dependents().cam_pos;
|
||||||
.read_storage::<Pos>()
|
|
||||||
.get(player_entity)
|
|
||||||
.map_or(Vec3::zero(), |pos| pos.0);
|
|
||||||
|
|
||||||
for (entity, pos, vel, body, physics, character) in (
|
for (entity, pos, vel, body, physics, character) in (
|
||||||
&ecs.entities(),
|
&ecs.entities(),
|
||||||
@ -55,9 +61,7 @@ impl EventMapper for MovementEventMapper {
|
|||||||
ecs.read_storage::<CharacterState>().maybe(),
|
ecs.read_storage::<CharacterState>().maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(_, e_pos, ..)| {
|
.filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR)
|
||||||
(e_pos.0.distance_squared(player_position)) < SFX_DIST_LIMIT_SQR
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
if let Some(character) = character {
|
if let Some(character) = character {
|
||||||
let state = self.event_history.entry(entity).or_default();
|
let state = self.event_history.entry(entity).or_default();
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
/// and triggers sfx for gaining experience and levelling up
|
/// and triggers sfx for gaining experience and levelling up
|
||||||
use super::EventMapper;
|
use super::EventMapper;
|
||||||
|
|
||||||
use crate::audio::sfx::{SfxEvent, SfxEventItem, SfxTriggers};
|
use crate::{
|
||||||
|
audio::sfx::{SfxEvent, SfxEventItem, SfxTriggers},
|
||||||
|
scene::Camera,
|
||||||
|
};
|
||||||
|
|
||||||
use common::{comp::Stats, event::EventBus, state::State};
|
use common::{comp::Stats, event::EventBus, state::State};
|
||||||
use specs::WorldExt;
|
use specs::WorldExt;
|
||||||
@ -23,7 +26,13 @@ pub struct ProgressionEventMapper {
|
|||||||
|
|
||||||
impl EventMapper for ProgressionEventMapper {
|
impl EventMapper for ProgressionEventMapper {
|
||||||
#[allow(clippy::op_ref)] // TODO: Pending review in #587
|
#[allow(clippy::op_ref)] // TODO: Pending review in #587
|
||||||
fn maintain(&mut self, state: &State, player_entity: specs::Entity, triggers: &SfxTriggers) {
|
fn maintain(
|
||||||
|
&mut self,
|
||||||
|
state: &State,
|
||||||
|
player_entity: specs::Entity,
|
||||||
|
_camera: &Camera,
|
||||||
|
triggers: &SfxTriggers,
|
||||||
|
) {
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
let next_state = ecs.read_storage::<Stats>().get(player_entity).map_or(
|
let next_state = ecs.read_storage::<Stats>().get(player_entity).map_or(
|
||||||
|
@ -83,15 +83,16 @@
|
|||||||
|
|
||||||
mod event_mapper;
|
mod event_mapper;
|
||||||
|
|
||||||
use crate::audio::AudioFrontend;
|
use crate::{audio::AudioFrontend, scene::Camera};
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
comp::{
|
comp::{
|
||||||
item::{ItemKind, ToolCategory},
|
item::{ItemKind, ToolCategory},
|
||||||
CharacterAbilityType, InventoryUpdateEvent, Ori, Pos,
|
CharacterAbilityType, InventoryUpdateEvent,
|
||||||
},
|
},
|
||||||
event::EventBus,
|
event::EventBus,
|
||||||
|
outcome::Outcome,
|
||||||
state::State,
|
state::State,
|
||||||
};
|
};
|
||||||
use event_mapper::SfxEventMapper;
|
use event_mapper::SfxEventMapper;
|
||||||
@ -146,6 +147,8 @@ pub enum SfxEvent {
|
|||||||
Wield(ToolCategory),
|
Wield(ToolCategory),
|
||||||
Unwield(ToolCategory),
|
Unwield(ToolCategory),
|
||||||
Inventory(SfxInventoryEvent),
|
Inventory(SfxInventoryEvent),
|
||||||
|
Explosion,
|
||||||
|
ProjectileShot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
||||||
@ -224,36 +227,27 @@ impl SfxMgr {
|
|||||||
audio: &mut AudioFrontend,
|
audio: &mut AudioFrontend,
|
||||||
state: &State,
|
state: &State,
|
||||||
player_entity: specs::Entity,
|
player_entity: specs::Entity,
|
||||||
|
camera: &Camera,
|
||||||
) {
|
) {
|
||||||
if !audio.sfx_enabled() {
|
if !audio.sfx_enabled() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.event_mapper
|
|
||||||
.maintain(state, player_entity, &self.triggers);
|
|
||||||
|
|
||||||
let ecs = state.ecs();
|
let ecs = state.ecs();
|
||||||
|
|
||||||
let player_position = ecs
|
audio.set_listener_pos(camera.dependents().cam_pos, camera.dependents().cam_dir);
|
||||||
.read_storage::<Pos>()
|
|
||||||
.get(player_entity)
|
|
||||||
.map_or(Vec3::zero(), |pos| pos.0);
|
|
||||||
|
|
||||||
let player_ori = *ecs
|
// TODO: replace; deprecated in favor of outcomes
|
||||||
.read_storage::<Ori>()
|
self.event_mapper
|
||||||
.get(player_entity)
|
.maintain(state, player_entity, camera, &self.triggers);
|
||||||
.copied()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.0;
|
|
||||||
|
|
||||||
audio.set_listener_pos(&player_position, &player_ori);
|
|
||||||
|
|
||||||
|
// TODO: replace; deprecated in favor of outcomes
|
||||||
let events = ecs.read_resource::<EventBus<SfxEventItem>>().recv_all();
|
let events = ecs.read_resource::<EventBus<SfxEventItem>>().recv_all();
|
||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
let position = match event.pos {
|
let position = match event.pos {
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
_ => player_position,
|
_ => camera.dependents().cam_pos,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(item) = self.triggers.get_trigger(&event.sfx) {
|
if let Some(item) = self.triggers.get_trigger(&event.sfx) {
|
||||||
@ -273,6 +267,31 @@ impl SfxMgr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_outcome(&mut self, outcome: &Outcome, audio: &mut AudioFrontend) {
|
||||||
|
if !audio.sfx_enabled() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match outcome {
|
||||||
|
Outcome::Explosion { pos, power } => {
|
||||||
|
audio.play_sfx(
|
||||||
|
// TODO: from sfx triggers config
|
||||||
|
"voxygen.audio.sfx.explosion",
|
||||||
|
*pos,
|
||||||
|
Some((*power / 2.5).min(1.5)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Outcome::ProjectileShot { pos, .. } => {
|
||||||
|
audio.play_sfx(
|
||||||
|
// TODO: from sfx triggers config
|
||||||
|
"voxygen.audio.sfx.glider_open",
|
||||||
|
*pos,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn load_sfx_items() -> SfxTriggers {
|
fn load_sfx_items() -> SfxTriggers {
|
||||||
match assets::load_file("voxygen.audio.sfx", &["ron"]) {
|
match assets::load_file("voxygen.audio.sfx", &["ron"]) {
|
||||||
Ok(file) => match ron::de::from_reader(file) {
|
Ok(file) => match ron::de::from_reader(file) {
|
||||||
|
@ -179,6 +179,7 @@ widget_ids! {
|
|||||||
entity_count,
|
entity_count,
|
||||||
num_chunks,
|
num_chunks,
|
||||||
num_figures,
|
num_figures,
|
||||||
|
num_particles,
|
||||||
|
|
||||||
// Game Version
|
// Game Version
|
||||||
version,
|
version,
|
||||||
@ -247,6 +248,8 @@ pub struct DebugInfo {
|
|||||||
pub num_visible_chunks: u32,
|
pub num_visible_chunks: u32,
|
||||||
pub num_figures: u32,
|
pub num_figures: u32,
|
||||||
pub num_figures_visible: u32,
|
pub num_figures_visible: u32,
|
||||||
|
pub num_particles: u32,
|
||||||
|
pub num_particles_visible: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HudInfo {
|
pub struct HudInfo {
|
||||||
@ -275,6 +278,7 @@ pub enum Event {
|
|||||||
ChangeGamma(f32),
|
ChangeGamma(f32),
|
||||||
MapZoom(f64),
|
MapZoom(f64),
|
||||||
AdjustWindowSize([u16; 2]),
|
AdjustWindowSize([u16; 2]),
|
||||||
|
ToggleParticlesEnabled(bool),
|
||||||
ToggleFullscreen,
|
ToggleFullscreen,
|
||||||
ChangeAaMode(AaMode),
|
ChangeAaMode(AaMode),
|
||||||
ChangeCloudMode(CloudMode),
|
ChangeCloudMode(CloudMode),
|
||||||
@ -1495,6 +1499,17 @@ impl Hud {
|
|||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.set(self.ids.num_figures, ui_widgets);
|
.set(self.ids.num_figures, ui_widgets);
|
||||||
|
|
||||||
|
// Number of particles
|
||||||
|
Text::new(&format!(
|
||||||
|
"Particles: {} ({} visible)",
|
||||||
|
debug_info.num_particles, debug_info.num_particles_visible,
|
||||||
|
))
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.down_from(self.ids.num_figures, 5.0)
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.set(self.ids.num_particles, ui_widgets);
|
||||||
|
|
||||||
// Help Window
|
// Help Window
|
||||||
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
|
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
|
||||||
Text::new(
|
Text::new(
|
||||||
@ -1504,7 +1519,7 @@ impl Hud {
|
|||||||
.replace("{key}", help_key.to_string().as_str()),
|
.replace("{key}", help_key.to_string().as_str()),
|
||||||
)
|
)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.down_from(self.ids.num_figures, 5.0)
|
.down_from(self.ids.num_particles, 5.0)
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.set(self.ids.help_info, ui_widgets);
|
.set(self.ids.help_info, ui_widgets);
|
||||||
@ -1896,6 +1911,9 @@ impl Hud {
|
|||||||
settings_window::Event::ChangeLanguage(language) => {
|
settings_window::Event::ChangeLanguage(language) => {
|
||||||
events.push(Event::ChangeLanguage(language));
|
events.push(Event::ChangeLanguage(language));
|
||||||
},
|
},
|
||||||
|
settings_window::Event::ToggleParticlesEnabled(particles_enabled) => {
|
||||||
|
events.push(Event::ToggleParticlesEnabled(particles_enabled));
|
||||||
|
},
|
||||||
settings_window::Event::ToggleFullscreen => {
|
settings_window::Event::ToggleFullscreen => {
|
||||||
events.push(Event::ToggleFullscreen);
|
events.push(Event::ToggleFullscreen);
|
||||||
},
|
},
|
||||||
|
@ -111,6 +111,8 @@ widget_ids! {
|
|||||||
cloud_mode_list,
|
cloud_mode_list,
|
||||||
fluid_mode_text,
|
fluid_mode_text,
|
||||||
fluid_mode_list,
|
fluid_mode_list,
|
||||||
|
particles_button,
|
||||||
|
particles_label,
|
||||||
fullscreen_button,
|
fullscreen_button,
|
||||||
fullscreen_label,
|
fullscreen_label,
|
||||||
save_window_size_button,
|
save_window_size_button,
|
||||||
@ -232,6 +234,7 @@ pub enum Event {
|
|||||||
AdjustFOV(u16),
|
AdjustFOV(u16),
|
||||||
AdjustGamma(f32),
|
AdjustGamma(f32),
|
||||||
AdjustWindowSize([u16; 2]),
|
AdjustWindowSize([u16; 2]),
|
||||||
|
ToggleParticlesEnabled(bool),
|
||||||
ToggleFullscreen,
|
ToggleFullscreen,
|
||||||
ChangeAaMode(AaMode),
|
ChangeAaMode(AaMode),
|
||||||
ChangeCloudMode(CloudMode),
|
ChangeCloudMode(CloudMode),
|
||||||
@ -2013,11 +2016,34 @@ impl<'a> Widget for SettingsWindow<'a> {
|
|||||||
events.push(Event::ChangeFluidMode(mode_list[clicked]));
|
events.push(Event::ChangeFluidMode(mode_list[clicked]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Particles
|
||||||
|
Text::new(&self.localized_strings.get("hud.settings.particles"))
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.down_from(state.ids.fluid_mode_list, 8.0)
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.set(state.ids.particles_label, ui);
|
||||||
|
|
||||||
|
let particles_enabled = ToggleButton::new(
|
||||||
|
self.global_state.settings.graphics.particles_enabled,
|
||||||
|
self.imgs.checkbox,
|
||||||
|
self.imgs.checkbox_checked,
|
||||||
|
)
|
||||||
|
.w_h(18.0, 18.0)
|
||||||
|
.right_from(state.ids.particles_label, 10.0)
|
||||||
|
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||||
|
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||||
|
.set(state.ids.particles_button, ui);
|
||||||
|
|
||||||
|
if self.global_state.settings.graphics.particles_enabled != particles_enabled {
|
||||||
|
events.push(Event::ToggleParticlesEnabled(particles_enabled));
|
||||||
|
}
|
||||||
|
|
||||||
// Fullscreen
|
// Fullscreen
|
||||||
Text::new(&self.localized_strings.get("hud.settings.fullscreen"))
|
Text::new(&self.localized_strings.get("hud.settings.fullscreen"))
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.down_from(state.ids.fluid_mode_list, 8.0)
|
.down_from(state.ids.particles_label, 8.0)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.set(state.ids.fullscreen_label, ui);
|
.set(state.ids.fullscreen_label, ui);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
mesh::{vol, Meshable},
|
mesh::{vol, Meshable},
|
||||||
render::{self, FigurePipeline, Mesh, SpritePipeline},
|
render::{self, FigurePipeline, Mesh, ParticlePipeline, SpritePipeline},
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
figure::Cell,
|
figure::Cell,
|
||||||
@ -11,6 +11,7 @@ use vek::*;
|
|||||||
|
|
||||||
type FigureVertex = <FigurePipeline as render::Pipeline>::Vertex;
|
type FigureVertex = <FigurePipeline as render::Pipeline>::Vertex;
|
||||||
type SpriteVertex = <SpritePipeline as render::Pipeline>::Vertex;
|
type SpriteVertex = <SpritePipeline as render::Pipeline>::Vertex;
|
||||||
|
type ParticleVertex = <ParticlePipeline as render::Pipeline>::Vertex;
|
||||||
|
|
||||||
impl<'a, V: 'a> Meshable<'a, FigurePipeline, FigurePipeline> for V
|
impl<'a, V: 'a> Meshable<'a, FigurePipeline, FigurePipeline> for V
|
||||||
where
|
where
|
||||||
@ -147,6 +148,73 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, V: 'a> Meshable<'a, ParticlePipeline, ParticlePipeline> for V
|
||||||
|
where
|
||||||
|
V: BaseVol<Vox = Cell> + ReadVol + SizedVol,
|
||||||
|
/* TODO: Use VolIterator instead of manually iterating
|
||||||
|
* &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>,
|
||||||
|
* &'a V: BaseVol<Vox=Cell>, */
|
||||||
|
{
|
||||||
|
type Pipeline = ParticlePipeline;
|
||||||
|
type Supplement = (Vec3<f32>, Vec3<f32>);
|
||||||
|
type TranslucentPipeline = ParticlePipeline;
|
||||||
|
|
||||||
|
#[allow(clippy::needless_range_loop)] // TODO: Pending review in #587
|
||||||
|
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||||
|
fn generate_mesh(
|
||||||
|
&'a self,
|
||||||
|
(offs, scale): Self::Supplement,
|
||||||
|
) -> (Mesh<Self::Pipeline>, Mesh<Self::TranslucentPipeline>) {
|
||||||
|
let mut mesh = Mesh::new();
|
||||||
|
|
||||||
|
let vol_iter = (self.lower_bound().x..self.upper_bound().x)
|
||||||
|
.map(|i| {
|
||||||
|
(self.lower_bound().y..self.upper_bound().y).map(move |j| {
|
||||||
|
(self.lower_bound().z..self.upper_bound().z).map(move |k| Vec3::new(i, j, k))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.flatten()
|
||||||
|
.map(|pos| (pos, self.get(pos).map(|x| *x).unwrap_or(Vox::empty())));
|
||||||
|
|
||||||
|
for (pos, vox) in vol_iter {
|
||||||
|
if let Some(col) = vox.get_color() {
|
||||||
|
vol::push_vox_verts(
|
||||||
|
&mut mesh,
|
||||||
|
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, light, ao| {
|
||||||
|
ParticleVertex::new(
|
||||||
|
origin * scale,
|
||||||
|
norm,
|
||||||
|
linear_to_srgb(srgb_to_linear(col) * light),
|
||||||
|
ao,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
&{
|
||||||
|
let mut ls = [[[None; 3]; 3]; 3];
|
||||||
|
for x in 0..3 {
|
||||||
|
for y in 0..3 {
|
||||||
|
for z in 0..3 {
|
||||||
|
ls[z][y][x] = self
|
||||||
|
.get(pos + Vec3::new(x as i32, y as i32, z as i32) - 1)
|
||||||
|
.map(|v| v.is_empty())
|
||||||
|
.unwrap_or(true)
|
||||||
|
.then_some(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ls
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(mesh, Mesh::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Use the 6 voxels/blocks surrounding the one at the specified position
|
/// Use the 6 voxels/blocks surrounding the one at the specified position
|
||||||
/// to detemine which faces should be drawn
|
/// to detemine which faces should be drawn
|
||||||
fn faces_to_make<V: ReadVol>(
|
fn faces_to_make<V: ReadVol>(
|
||||||
|
@ -18,6 +18,7 @@ pub use self::{
|
|||||||
pipelines::{
|
pipelines::{
|
||||||
figure::{BoneData as FigureBoneData, FigurePipeline, Locals as FigureLocals},
|
figure::{BoneData as FigureBoneData, FigurePipeline, Locals as FigureLocals},
|
||||||
fluid::FluidPipeline,
|
fluid::FluidPipeline,
|
||||||
|
particle::{Instance as ParticleInstance, ParticlePipeline},
|
||||||
postprocess::{
|
postprocess::{
|
||||||
create_mesh as create_pp_mesh, Locals as PostProcessLocals, PostProcessPipeline,
|
create_mesh as create_pp_mesh, Locals as PostProcessLocals, PostProcessPipeline,
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub mod figure;
|
pub mod figure;
|
||||||
pub mod fluid;
|
pub mod fluid;
|
||||||
|
pub mod particle;
|
||||||
pub mod postprocess;
|
pub mod postprocess;
|
||||||
pub mod skybox;
|
pub mod skybox;
|
||||||
pub mod sprite;
|
pub mod sprite;
|
||||||
@ -116,6 +117,11 @@ impl Light {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pos(&self) -> Vec3<f32> { Vec3::new(self.pos[0], self.pos[1], self.pos[2]) }
|
pub fn get_pos(&self) -> Vec3<f32> { Vec3::new(self.pos[0], self.pos[1], self.pos[2]) }
|
||||||
|
|
||||||
|
pub fn with_strength(mut self, strength: f32) -> Self {
|
||||||
|
self.col = (Vec4::<f32>::from(self.col) * strength).into_array();
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Light {
|
impl Default for Light {
|
||||||
|
116
voxygen/src/render/pipelines/particle.rs
Normal file
116
voxygen/src/render/pipelines/particle.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
use super::{
|
||||||
|
super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt},
|
||||||
|
Globals, Light, Shadow,
|
||||||
|
};
|
||||||
|
use gfx::{
|
||||||
|
self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner,
|
||||||
|
gfx_vertex_struct_meta,
|
||||||
|
state::{ColorMask, Comparison, Stencil, StencilOp},
|
||||||
|
};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
gfx_defines! {
|
||||||
|
vertex Vertex {
|
||||||
|
pos: [f32; 3] = "v_pos",
|
||||||
|
// ____BBBBBBBBGGGGGGGGRRRRRRRR
|
||||||
|
col: u32 = "v_col",
|
||||||
|
// ...AANNN
|
||||||
|
// A = AO
|
||||||
|
// N = Normal
|
||||||
|
norm_ao: u32 = "v_norm_ao",
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex Instance {
|
||||||
|
// created_at time, so we can calculate time relativity, needed for relative animation.
|
||||||
|
// can save 32 bits per instance, for particles that are not relatively animated.
|
||||||
|
inst_time: f32 = "inst_time",
|
||||||
|
|
||||||
|
// a seed value for randomness
|
||||||
|
// can save 32 bits per instance, for particles that don't need randomness/uniqueness.
|
||||||
|
inst_entropy: f32 = "inst_entropy",
|
||||||
|
|
||||||
|
// modes should probably be seperate shaders, as a part of scaling and optimisation efforts.
|
||||||
|
// can save 32 bits per instance, and have cleaner tailor made code.
|
||||||
|
inst_mode: i32 = "inst_mode",
|
||||||
|
|
||||||
|
// a triangle is: f32 x 3 x 3 x 1 = 288 bits
|
||||||
|
// a quad is: f32 x 3 x 3 x 2 = 576 bits
|
||||||
|
// a cube is: f32 x 3 x 3 x 12 = 3456 bits
|
||||||
|
// this vec is: f32 x 3 x 1 x 1 = 96 bits (per instance!)
|
||||||
|
// consider using a throw-away mesh and
|
||||||
|
// positioning the vertex verticies instead,
|
||||||
|
// if we have:
|
||||||
|
// - a triangle mesh, and 3 or more instances.
|
||||||
|
// - a quad mesh, and 6 or more instances.
|
||||||
|
// - a cube mesh, and 36 or more instances.
|
||||||
|
inst_pos: [f32; 3] = "inst_pos",
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline pipe {
|
||||||
|
vbuf: gfx::VertexBuffer<Vertex> = (),
|
||||||
|
ibuf: gfx::InstanceBuffer<Instance> = (),
|
||||||
|
|
||||||
|
globals: gfx::ConstantBuffer<Globals> = "u_globals",
|
||||||
|
lights: gfx::ConstantBuffer<Light> = "u_lights",
|
||||||
|
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
|
||||||
|
|
||||||
|
noise: gfx::TextureSampler<f32> = "t_noise",
|
||||||
|
|
||||||
|
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA),
|
||||||
|
tgt_depth_stencil: gfx::DepthStencilTarget<TgtDepthStencilFmt> = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vertex {
|
||||||
|
#[allow(clippy::collapsible_if)]
|
||||||
|
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>, ao: f32) -> Self {
|
||||||
|
let norm_bits = if norm.x != 0.0 {
|
||||||
|
if norm.x < 0.0 { 0 } else { 1 }
|
||||||
|
} else if norm.y != 0.0 {
|
||||||
|
if norm.y < 0.0 { 2 } else { 3 }
|
||||||
|
} else {
|
||||||
|
if norm.z < 0.0 { 4 } else { 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
pos: pos.into_array(),
|
||||||
|
col: col
|
||||||
|
.map2(Rgb::new(0, 8, 16), |e, shift| ((e * 255.0) as u32) << shift)
|
||||||
|
.reduce_bitor(),
|
||||||
|
norm_ao: norm_bits | (((ao * 3.9999) as u32) << 3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ParticleMode {
|
||||||
|
CampfireSmoke = 0,
|
||||||
|
CampfireFire = 1,
|
||||||
|
GunPowderSpark = 2,
|
||||||
|
Shrapnel = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParticleMode {
|
||||||
|
pub fn into_uint(self) -> u32 { self as u32 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
pub fn new(inst_time: f64, inst_mode: ParticleMode, inst_pos: Vec3<f32>) -> Self {
|
||||||
|
use rand::Rng;
|
||||||
|
Self {
|
||||||
|
inst_time: inst_time as f32,
|
||||||
|
inst_entropy: rand::thread_rng().gen(),
|
||||||
|
inst_mode: inst_mode as i32,
|
||||||
|
inst_pos: inst_pos.into_array(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Instance {
|
||||||
|
fn default() -> Self { Self::new(0.0, ParticleMode::CampfireSmoke, Vec3::zero()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ParticlePipeline;
|
||||||
|
|
||||||
|
impl Pipeline for ParticlePipeline {
|
||||||
|
type Vertex = Vertex;
|
||||||
|
}
|
@ -4,7 +4,9 @@ use super::{
|
|||||||
instances::Instances,
|
instances::Instances,
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
model::{DynamicModel, Model},
|
model::{DynamicModel, Model},
|
||||||
pipelines::{figure, fluid, postprocess, skybox, sprite, terrain, ui, Globals, Light, Shadow},
|
pipelines::{
|
||||||
|
figure, fluid, particle, postprocess, skybox, sprite, terrain, ui, Globals, Light, Shadow,
|
||||||
|
},
|
||||||
texture::Texture,
|
texture::Texture,
|
||||||
AaMode, CloudMode, FluidMode, Pipeline, RenderError,
|
AaMode, CloudMode, FluidMode, Pipeline, RenderError,
|
||||||
};
|
};
|
||||||
@ -70,6 +72,7 @@ pub struct Renderer {
|
|||||||
terrain_pipeline: GfxPipeline<terrain::pipe::Init<'static>>,
|
terrain_pipeline: GfxPipeline<terrain::pipe::Init<'static>>,
|
||||||
fluid_pipeline: GfxPipeline<fluid::pipe::Init<'static>>,
|
fluid_pipeline: GfxPipeline<fluid::pipe::Init<'static>>,
|
||||||
sprite_pipeline: GfxPipeline<sprite::pipe::Init<'static>>,
|
sprite_pipeline: GfxPipeline<sprite::pipe::Init<'static>>,
|
||||||
|
particle_pipeline: GfxPipeline<particle::pipe::Init<'static>>,
|
||||||
ui_pipeline: GfxPipeline<ui::pipe::Init<'static>>,
|
ui_pipeline: GfxPipeline<ui::pipe::Init<'static>>,
|
||||||
postprocess_pipeline: GfxPipeline<postprocess::pipe::Init<'static>>,
|
postprocess_pipeline: GfxPipeline<postprocess::pipe::Init<'static>>,
|
||||||
player_shadow_pipeline: GfxPipeline<figure::pipe::Init<'static>>,
|
player_shadow_pipeline: GfxPipeline<figure::pipe::Init<'static>>,
|
||||||
@ -103,6 +106,7 @@ impl Renderer {
|
|||||||
terrain_pipeline,
|
terrain_pipeline,
|
||||||
fluid_pipeline,
|
fluid_pipeline,
|
||||||
sprite_pipeline,
|
sprite_pipeline,
|
||||||
|
particle_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
postprocess_pipeline,
|
postprocess_pipeline,
|
||||||
player_shadow_pipeline,
|
player_shadow_pipeline,
|
||||||
@ -146,6 +150,7 @@ impl Renderer {
|
|||||||
terrain_pipeline,
|
terrain_pipeline,
|
||||||
fluid_pipeline,
|
fluid_pipeline,
|
||||||
sprite_pipeline,
|
sprite_pipeline,
|
||||||
|
particle_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
postprocess_pipeline,
|
postprocess_pipeline,
|
||||||
player_shadow_pipeline,
|
player_shadow_pipeline,
|
||||||
@ -341,6 +346,7 @@ impl Renderer {
|
|||||||
terrain_pipeline,
|
terrain_pipeline,
|
||||||
fluid_pipeline,
|
fluid_pipeline,
|
||||||
sprite_pipeline,
|
sprite_pipeline,
|
||||||
|
particle_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
postprocess_pipeline,
|
postprocess_pipeline,
|
||||||
player_shadow_pipeline,
|
player_shadow_pipeline,
|
||||||
@ -350,6 +356,7 @@ impl Renderer {
|
|||||||
self.terrain_pipeline = terrain_pipeline;
|
self.terrain_pipeline = terrain_pipeline;
|
||||||
self.fluid_pipeline = fluid_pipeline;
|
self.fluid_pipeline = fluid_pipeline;
|
||||||
self.sprite_pipeline = sprite_pipeline;
|
self.sprite_pipeline = sprite_pipeline;
|
||||||
|
self.particle_pipeline = particle_pipeline;
|
||||||
self.ui_pipeline = ui_pipeline;
|
self.ui_pipeline = ui_pipeline;
|
||||||
self.postprocess_pipeline = postprocess_pipeline;
|
self.postprocess_pipeline = postprocess_pipeline;
|
||||||
self.player_shadow_pipeline = player_shadow_pipeline;
|
self.player_shadow_pipeline = player_shadow_pipeline;
|
||||||
@ -711,6 +718,37 @@ impl Renderer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Queue the rendering of the provided particle in the upcoming frame.
|
||||||
|
pub fn render_particles(
|
||||||
|
&mut self,
|
||||||
|
model: &Model<particle::ParticlePipeline>,
|
||||||
|
globals: &Consts<Globals>,
|
||||||
|
instances: &Instances<particle::Instance>,
|
||||||
|
lights: &Consts<Light>,
|
||||||
|
shadows: &Consts<Shadow>,
|
||||||
|
) {
|
||||||
|
self.encoder.draw(
|
||||||
|
&gfx::Slice {
|
||||||
|
start: model.vertex_range().start,
|
||||||
|
end: model.vertex_range().end,
|
||||||
|
base_vertex: 0,
|
||||||
|
instances: Some((instances.count() as u32, 0)),
|
||||||
|
buffer: gfx::IndexBuffer::Auto,
|
||||||
|
},
|
||||||
|
&self.particle_pipeline.pso,
|
||||||
|
&particle::pipe::Data {
|
||||||
|
vbuf: model.vbuf.clone(),
|
||||||
|
ibuf: instances.ibuf.clone(),
|
||||||
|
globals: globals.buf.clone(),
|
||||||
|
lights: lights.buf.clone(),
|
||||||
|
shadows: shadows.buf.clone(),
|
||||||
|
noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
|
||||||
|
tgt_color: self.tgt_color_view.clone(),
|
||||||
|
tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Queue the rendering of the provided UI element in the upcoming frame.
|
/// Queue the rendering of the provided UI element in the upcoming frame.
|
||||||
pub fn render_ui_element(
|
pub fn render_ui_element(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -793,6 +831,7 @@ fn create_pipelines(
|
|||||||
GfxPipeline<terrain::pipe::Init<'static>>,
|
GfxPipeline<terrain::pipe::Init<'static>>,
|
||||||
GfxPipeline<fluid::pipe::Init<'static>>,
|
GfxPipeline<fluid::pipe::Init<'static>>,
|
||||||
GfxPipeline<sprite::pipe::Init<'static>>,
|
GfxPipeline<sprite::pipe::Init<'static>>,
|
||||||
|
GfxPipeline<particle::pipe::Init<'static>>,
|
||||||
GfxPipeline<ui::pipe::Init<'static>>,
|
GfxPipeline<ui::pipe::Init<'static>>,
|
||||||
GfxPipeline<postprocess::pipe::Init<'static>>,
|
GfxPipeline<postprocess::pipe::Init<'static>>,
|
||||||
GfxPipeline<figure::pipe::Init<'static>>,
|
GfxPipeline<figure::pipe::Init<'static>>,
|
||||||
@ -914,6 +953,18 @@ fn create_pipelines(
|
|||||||
gfx::state::CullFace::Back,
|
gfx::state::CullFace::Back,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Construct a pipeline for rendering particles
|
||||||
|
let particle_pipeline = create_pipeline(
|
||||||
|
factory,
|
||||||
|
particle::pipe::new(),
|
||||||
|
&assets::load_watched::<String>("voxygen.shaders.particle-vert", shader_reload_indicator)
|
||||||
|
.unwrap(),
|
||||||
|
&assets::load_watched::<String>("voxygen.shaders.particle-frag", shader_reload_indicator)
|
||||||
|
.unwrap(),
|
||||||
|
&include_ctx,
|
||||||
|
gfx::state::CullFace::Back,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Construct a pipeline for rendering UI elements
|
// Construct a pipeline for rendering UI elements
|
||||||
let ui_pipeline = create_pipeline(
|
let ui_pipeline = create_pipeline(
|
||||||
factory,
|
factory,
|
||||||
@ -975,6 +1026,7 @@ fn create_pipelines(
|
|||||||
terrain_pipeline,
|
terrain_pipeline,
|
||||||
fluid_pipeline,
|
fluid_pipeline,
|
||||||
sprite_pipeline,
|
sprite_pipeline,
|
||||||
|
particle_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
postprocess_pipeline,
|
postprocess_pipeline,
|
||||||
player_shadow_pipeline,
|
player_shadow_pipeline,
|
||||||
|
@ -24,11 +24,12 @@ impl Default for CameraMode {
|
|||||||
fn default() -> Self { Self::ThirdPerson }
|
fn default() -> Self { Self::ThirdPerson }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Dependents {
|
pub struct Dependents {
|
||||||
pub view_mat: Mat4<f32>,
|
pub view_mat: Mat4<f32>,
|
||||||
pub proj_mat: Mat4<f32>,
|
pub proj_mat: Mat4<f32>,
|
||||||
pub cam_pos: Vec3<f32>,
|
pub cam_pos: Vec3<f32>,
|
||||||
|
pub cam_dir: Vec3<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
@ -67,6 +68,7 @@ impl Camera {
|
|||||||
view_mat: Mat4::identity(),
|
view_mat: Mat4::identity(),
|
||||||
proj_mat: Mat4::identity(),
|
proj_mat: Mat4::identity(),
|
||||||
cam_pos: Vec3::zero(),
|
cam_pos: Vec3::zero(),
|
||||||
|
cam_dir: Vec3::unit_y(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,6 +106,8 @@ impl Camera {
|
|||||||
|
|
||||||
// TODO: Make this more efficient.
|
// TODO: Make this more efficient.
|
||||||
self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w());
|
self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w());
|
||||||
|
|
||||||
|
self.dependents.cam_dir = Vec3::from(self.dependents.view_mat.inverted() * -Vec4::unit_z());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frustum(&self) -> Frustum<f32> {
|
pub fn frustum(&self) -> Frustum<f32> {
|
||||||
@ -112,7 +116,7 @@ impl Camera {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dependents(&self) -> Dependents { self.dependents.clone() }
|
pub fn dependents(&self) -> Dependents { self.dependents }
|
||||||
|
|
||||||
/// Rotate the camera about its focus by the given delta, limiting the input
|
/// Rotate the camera about its focus by the given delta, limiting the input
|
||||||
/// accordingly.
|
/// accordingly.
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub mod figure;
|
pub mod figure;
|
||||||
|
pub mod particle;
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
|
|
||||||
use self::{
|
pub use self::{
|
||||||
camera::{Camera, CameraMode},
|
camera::{Camera, CameraMode},
|
||||||
figure::FigureMgr,
|
figure::FigureMgr,
|
||||||
|
particle::ParticleMgr,
|
||||||
terrain::Terrain,
|
terrain::Terrain,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -19,7 +21,8 @@ use crate::{
|
|||||||
use anim::character::SkeletonAttr;
|
use anim::character::SkeletonAttr;
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
state::State,
|
outcome::Outcome,
|
||||||
|
state::{DeltaTime, State},
|
||||||
terrain::{BlockKind, TerrainChunk},
|
terrain::{BlockKind, TerrainChunk},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
@ -39,6 +42,12 @@ const SHADOW_MAX_DIST: f32 = 96.0; // The distance beyond which shadows may not
|
|||||||
/// Used for first person camera effects
|
/// Used for first person camera effects
|
||||||
const RUNNING_THRESHOLD: f32 = 0.7;
|
const RUNNING_THRESHOLD: f32 = 0.7;
|
||||||
|
|
||||||
|
struct EventLight {
|
||||||
|
light: Light,
|
||||||
|
timeout: f32,
|
||||||
|
fadeout: fn(f32) -> f32,
|
||||||
|
}
|
||||||
|
|
||||||
struct Skybox {
|
struct Skybox {
|
||||||
model: Model<SkyboxPipeline>,
|
model: Model<SkyboxPipeline>,
|
||||||
locals: Consts<SkyboxLocals>,
|
locals: Consts<SkyboxLocals>,
|
||||||
@ -55,6 +64,7 @@ pub struct Scene {
|
|||||||
shadows: Consts<Shadow>,
|
shadows: Consts<Shadow>,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
camera_input_state: Vec2<f32>,
|
camera_input_state: Vec2<f32>,
|
||||||
|
event_lights: Vec<EventLight>,
|
||||||
|
|
||||||
skybox: Skybox,
|
skybox: Skybox,
|
||||||
postprocess: PostProcess,
|
postprocess: PostProcess,
|
||||||
@ -62,6 +72,7 @@ pub struct Scene {
|
|||||||
loaded_distance: f32,
|
loaded_distance: f32,
|
||||||
select_pos: Option<Vec3<i32>>,
|
select_pos: Option<Vec3<i32>>,
|
||||||
|
|
||||||
|
particle_mgr: ParticleMgr,
|
||||||
figure_mgr: FigureMgr,
|
figure_mgr: FigureMgr,
|
||||||
sfx_mgr: SfxMgr,
|
sfx_mgr: SfxMgr,
|
||||||
music_mgr: MusicMgr,
|
music_mgr: MusicMgr,
|
||||||
@ -78,6 +89,7 @@ pub struct SceneData<'a> {
|
|||||||
pub gamma: f32,
|
pub gamma: f32,
|
||||||
pub mouse_smoothing: bool,
|
pub mouse_smoothing: bool,
|
||||||
pub sprite_render_distance: f32,
|
pub sprite_render_distance: f32,
|
||||||
|
pub particles_enabled: bool,
|
||||||
pub figure_lod_render_distance: f32,
|
pub figure_lod_render_distance: f32,
|
||||||
pub is_aiming: bool,
|
pub is_aiming: bool,
|
||||||
}
|
}
|
||||||
@ -97,6 +109,7 @@ impl Scene {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson),
|
camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson),
|
||||||
camera_input_state: Vec2::zero(),
|
camera_input_state: Vec2::zero(),
|
||||||
|
event_lights: Vec::new(),
|
||||||
|
|
||||||
skybox: Skybox {
|
skybox: Skybox {
|
||||||
model: renderer.create_model(&create_skybox_mesh()).unwrap(),
|
model: renderer.create_model(&create_skybox_mesh()).unwrap(),
|
||||||
@ -112,6 +125,7 @@ impl Scene {
|
|||||||
loaded_distance: 0.0,
|
loaded_distance: 0.0,
|
||||||
select_pos: None,
|
select_pos: None,
|
||||||
|
|
||||||
|
particle_mgr: ParticleMgr::new(renderer),
|
||||||
figure_mgr: FigureMgr::new(),
|
figure_mgr: FigureMgr::new(),
|
||||||
sfx_mgr: SfxMgr::new(),
|
sfx_mgr: SfxMgr::new(),
|
||||||
music_mgr: MusicMgr::new(),
|
music_mgr: MusicMgr::new(),
|
||||||
@ -127,6 +141,9 @@ impl Scene {
|
|||||||
/// Get a reference to the scene's terrain.
|
/// Get a reference to the scene's terrain.
|
||||||
pub fn terrain(&self) -> &Terrain<TerrainChunk> { &self.terrain }
|
pub fn terrain(&self) -> &Terrain<TerrainChunk> { &self.terrain }
|
||||||
|
|
||||||
|
/// Get a reference to the scene's particle manager.
|
||||||
|
pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr }
|
||||||
|
|
||||||
/// Get a reference to the scene's figure manager.
|
/// Get a reference to the scene's figure manager.
|
||||||
pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr }
|
pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr }
|
||||||
|
|
||||||
@ -176,6 +193,25 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_outcome(
|
||||||
|
&mut self,
|
||||||
|
outcome: &Outcome,
|
||||||
|
scene_data: &SceneData,
|
||||||
|
audio: &mut AudioFrontend,
|
||||||
|
) {
|
||||||
|
self.particle_mgr.handle_outcome(&outcome, &scene_data);
|
||||||
|
self.sfx_mgr.handle_outcome(&outcome, audio);
|
||||||
|
|
||||||
|
match outcome {
|
||||||
|
Outcome::Explosion { pos, power, .. } => self.event_lights.push(EventLight {
|
||||||
|
light: Light::new(*pos, Rgb::new(1.0, 0.5, 0.0), *power * 2.5),
|
||||||
|
timeout: 0.5,
|
||||||
|
fadeout: |timeout| timeout * 2.0,
|
||||||
|
}),
|
||||||
|
Outcome::ProjectileShot { .. } => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Maintain data such as GPU constant buffers, models, etc. To be called
|
/// Maintain data such as GPU constant buffers, models, etc. To be called
|
||||||
/// once per tick.
|
/// once per tick.
|
||||||
pub fn maintain(
|
pub fn maintain(
|
||||||
@ -265,6 +301,7 @@ impl Scene {
|
|||||||
view_mat,
|
view_mat,
|
||||||
proj_mat,
|
proj_mat,
|
||||||
cam_pos,
|
cam_pos,
|
||||||
|
..
|
||||||
} = self.camera.dependents();
|
} = self.camera.dependents();
|
||||||
|
|
||||||
// Update chunk loaded distance smoothly for nice shader fog
|
// Update chunk loaded distance smoothly for nice shader fog
|
||||||
@ -307,6 +344,11 @@ impl Scene {
|
|||||||
light_anim.strength,
|
light_anim.strength,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
.chain(
|
||||||
|
self.event_lights
|
||||||
|
.iter()
|
||||||
|
.map(|el| el.light.with_strength((el.fadeout)(el.timeout))),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32);
|
lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32);
|
||||||
lights.truncate(MAX_LIGHT_COUNT);
|
lights.truncate(MAX_LIGHT_COUNT);
|
||||||
@ -314,6 +356,13 @@ impl Scene {
|
|||||||
.update_consts(&mut self.lights, &lights)
|
.update_consts(&mut self.lights, &lights)
|
||||||
.expect("Failed to update light constants");
|
.expect("Failed to update light constants");
|
||||||
|
|
||||||
|
// Update event lights
|
||||||
|
let dt = ecs.fetch::<DeltaTime>().0;
|
||||||
|
self.event_lights.drain_filter(|el| {
|
||||||
|
el.timeout -= dt;
|
||||||
|
el.timeout <= 0.0
|
||||||
|
});
|
||||||
|
|
||||||
// Update shadow constants
|
// Update shadow constants
|
||||||
let mut shadows = (
|
let mut shadows = (
|
||||||
&scene_data.state.ecs().read_storage::<comp::Pos>(),
|
&scene_data.state.ecs().read_storage::<comp::Pos>(),
|
||||||
@ -388,9 +437,16 @@ impl Scene {
|
|||||||
// Remove unused figures.
|
// Remove unused figures.
|
||||||
self.figure_mgr.clean(scene_data.tick);
|
self.figure_mgr.clean(scene_data.tick);
|
||||||
|
|
||||||
|
// Maintain the particles.
|
||||||
|
self.particle_mgr.maintain(renderer, &scene_data);
|
||||||
|
|
||||||
// Maintain audio
|
// Maintain audio
|
||||||
self.sfx_mgr
|
self.sfx_mgr.maintain(
|
||||||
.maintain(audio, scene_data.state, scene_data.player_entity);
|
audio,
|
||||||
|
scene_data.state,
|
||||||
|
scene_data.player_entity,
|
||||||
|
&self.camera,
|
||||||
|
);
|
||||||
self.music_mgr.maintain(audio, scene_data.state);
|
self.music_mgr.maintain(audio, scene_data.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,6 +479,14 @@ impl Scene {
|
|||||||
scene_data.figure_lod_render_distance,
|
scene_data.figure_lod_render_distance,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.particle_mgr.render(
|
||||||
|
renderer,
|
||||||
|
scene_data,
|
||||||
|
&self.globals,
|
||||||
|
&self.lights,
|
||||||
|
&self.shadows,
|
||||||
|
);
|
||||||
|
|
||||||
// Render the skybox.
|
// Render the skybox.
|
||||||
renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals);
|
renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals);
|
||||||
|
|
||||||
|
369
voxygen/src/scene/particle.rs
Normal file
369
voxygen/src/scene/particle.rs
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
use super::SceneData;
|
||||||
|
use crate::{
|
||||||
|
mesh::Meshable,
|
||||||
|
render::{
|
||||||
|
pipelines::particle::ParticleMode, Consts, Globals, Instances, Light, Model,
|
||||||
|
ParticleInstance, ParticlePipeline, Renderer, Shadow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use common::{
|
||||||
|
assets,
|
||||||
|
comp::{object, Body, CharacterState, Pos},
|
||||||
|
figure::Segment,
|
||||||
|
outcome::Outcome,
|
||||||
|
};
|
||||||
|
use dot_vox::DotVoxData;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use rand::Rng;
|
||||||
|
use specs::{Join, WorldExt};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
pub struct ParticleMgr {
|
||||||
|
/// keep track of lifespans
|
||||||
|
particles: Vec<Particle>,
|
||||||
|
|
||||||
|
/// keep track of timings
|
||||||
|
scheduler: HeartbeatScheduler,
|
||||||
|
|
||||||
|
/// GPU Instance Buffer
|
||||||
|
instances: Instances<ParticleInstance>,
|
||||||
|
|
||||||
|
/// GPU Vertex Buffers
|
||||||
|
model_cache: HashMap<&'static str, Model<ParticlePipeline>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParticleMgr {
|
||||||
|
pub fn new(renderer: &mut Renderer) -> Self {
|
||||||
|
Self {
|
||||||
|
particles: Vec::new(),
|
||||||
|
scheduler: HeartbeatScheduler::new(),
|
||||||
|
instances: default_instances(renderer),
|
||||||
|
model_cache: default_cache(renderer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_outcome(&mut self, outcome: &Outcome, scene_data: &SceneData) {
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
match outcome {
|
||||||
|
Outcome::Explosion { pos, power } => {
|
||||||
|
for _ in 0..150 {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_millis(250),
|
||||||
|
time,
|
||||||
|
ParticleMode::Shrapnel,
|
||||||
|
*pos,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for _ in 0..200 {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_secs(4),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireSmoke,
|
||||||
|
*pos + Vec2::<f32>::zero().map(|_| rng.gen_range(-1.0, 1.0) * power),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Outcome::ProjectileShot { .. } => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) {
|
||||||
|
if scene_data.particles_enabled {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
// remove dead Particle
|
||||||
|
self.particles.retain(|p| p.alive_until > now);
|
||||||
|
|
||||||
|
// add new Particle
|
||||||
|
self.maintain_body_particles(scene_data);
|
||||||
|
self.maintain_boost_particles(scene_data);
|
||||||
|
|
||||||
|
// update timings
|
||||||
|
self.scheduler.maintain();
|
||||||
|
} else {
|
||||||
|
// remove all particle lifespans
|
||||||
|
self.particles.clear();
|
||||||
|
|
||||||
|
// remove all timings
|
||||||
|
self.scheduler.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.upload_particles(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_body_particles(&mut self, scene_data: &SceneData) {
|
||||||
|
let ecs = scene_data.state.ecs();
|
||||||
|
for (_i, (_entity, body, pos)) in (
|
||||||
|
&ecs.entities(),
|
||||||
|
&ecs.read_storage::<Body>(),
|
||||||
|
&ecs.read_storage::<Pos>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
match body {
|
||||||
|
Body::Object(object::Body::CampfireLit) => {
|
||||||
|
self.maintain_campfirelit_particles(scene_data, pos)
|
||||||
|
},
|
||||||
|
Body::Object(object::Body::BoltFire) => {
|
||||||
|
self.maintain_boltfire_particles(scene_data, pos)
|
||||||
|
},
|
||||||
|
Body::Object(object::Body::BoltFireBig) => {
|
||||||
|
self.maintain_boltfirebig_particles(scene_data, pos)
|
||||||
|
},
|
||||||
|
Body::Object(object::Body::Bomb) => self.maintain_bomb_particles(scene_data, pos),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_campfirelit_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_millis(250),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireFire,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireSmoke,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_boltfire_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_millis(250),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireFire,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_secs(1),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireSmoke,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_boltfirebig_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
|
||||||
|
// fire
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(3)) {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_millis(250),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireFire,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// smoke
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_secs(2),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireSmoke,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) {
|
||||||
|
// sparks
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_millis(1500),
|
||||||
|
time,
|
||||||
|
ParticleMode::GunPowderSpark,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
// smoke
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_secs(2),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireSmoke,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maintain_boost_particles(&mut self, scene_data: &SceneData) {
|
||||||
|
let state = scene_data.state;
|
||||||
|
let ecs = state.ecs();
|
||||||
|
let time = state.get_time();
|
||||||
|
|
||||||
|
for (_i, (_entity, pos, character_state)) in (
|
||||||
|
&ecs.entities(),
|
||||||
|
&ecs.read_storage::<Pos>(),
|
||||||
|
&ecs.read_storage::<CharacterState>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if let CharacterState::Boost(_) = character_state {
|
||||||
|
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) {
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_secs(15),
|
||||||
|
time,
|
||||||
|
ParticleMode::CampfireSmoke,
|
||||||
|
pos.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upload_particles(&mut self, renderer: &mut Renderer) {
|
||||||
|
let all_cpu_instances = self
|
||||||
|
.particles
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.instance)
|
||||||
|
.collect::<Vec<ParticleInstance>>();
|
||||||
|
|
||||||
|
// TODO: optimise buffer writes
|
||||||
|
let gpu_instances = renderer
|
||||||
|
.create_instances(&all_cpu_instances)
|
||||||
|
.expect("Failed to upload particle instances to the GPU!");
|
||||||
|
|
||||||
|
self.instances = gpu_instances;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
scene_data: &SceneData,
|
||||||
|
globals: &Consts<Globals>,
|
||||||
|
lights: &Consts<Light>,
|
||||||
|
shadows: &Consts<Shadow>,
|
||||||
|
) {
|
||||||
|
if scene_data.particles_enabled {
|
||||||
|
let model = &self
|
||||||
|
.model_cache
|
||||||
|
.get(DEFAULT_MODEL_KEY)
|
||||||
|
.expect("Expected particle model in cache");
|
||||||
|
|
||||||
|
renderer.render_particles(model, globals, &self.instances, lights, shadows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn particle_count(&self) -> usize { self.instances.count() }
|
||||||
|
|
||||||
|
pub fn particle_count_visible(&self) -> usize { self.instances.count() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_instances(renderer: &mut Renderer) -> Instances<ParticleInstance> {
|
||||||
|
let empty_vec = Vec::new();
|
||||||
|
|
||||||
|
renderer
|
||||||
|
.create_instances(&empty_vec)
|
||||||
|
.expect("Failed to upload particle instances to the GPU!")
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_MODEL_KEY: &str = "voxygen.voxel.particle";
|
||||||
|
|
||||||
|
fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model<ParticlePipeline>> {
|
||||||
|
let mut model_cache = HashMap::new();
|
||||||
|
|
||||||
|
model_cache.entry(DEFAULT_MODEL_KEY).or_insert_with(|| {
|
||||||
|
let offset = Vec3::zero();
|
||||||
|
let lod_scale = Vec3::one();
|
||||||
|
|
||||||
|
let vox = assets::load_expect::<DotVoxData>(DEFAULT_MODEL_KEY);
|
||||||
|
|
||||||
|
let mesh = &Meshable::<ParticlePipeline, ParticlePipeline>::generate_mesh(
|
||||||
|
&Segment::from(vox.as_ref()),
|
||||||
|
(offset * lod_scale, Vec3::one() / lod_scale),
|
||||||
|
)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
renderer
|
||||||
|
.create_model(mesh)
|
||||||
|
.expect("Failed to create particle model")
|
||||||
|
});
|
||||||
|
|
||||||
|
model_cache
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accumulates heartbeats to be consumed on the next tick.
|
||||||
|
struct HeartbeatScheduler {
|
||||||
|
/// Duration = Heartbeat Frequency/Intervals
|
||||||
|
/// Instant = Last update time
|
||||||
|
/// u8 = number of heartbeats since last update
|
||||||
|
/// - if it's more frequent then tick rate, it could be 1 or more.
|
||||||
|
/// - if it's less frequent then tick rate, it could be 1 or 0.
|
||||||
|
/// - if it's equal to the tick rate, it could be between 2 and 0, due to
|
||||||
|
/// delta time variance etc.
|
||||||
|
timers: HashMap<Duration, (Instant, u8)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeartbeatScheduler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
HeartbeatScheduler {
|
||||||
|
timers: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// updates the last elapsed times and elasped counts
|
||||||
|
/// this should be called once, and only once per tick.
|
||||||
|
pub fn maintain(&mut self) {
|
||||||
|
for (frequency, (last_update, heartbeats)) in self.timers.iter_mut() {
|
||||||
|
// the number of iterations since last update
|
||||||
|
*heartbeats =
|
||||||
|
// TODO: use nightly api once stable; https://github.com/rust-lang/rust/issues/63139
|
||||||
|
(last_update.elapsed().as_secs_f32() / frequency.as_secs_f32()).floor() as u8;
|
||||||
|
|
||||||
|
// Instant::now() minus the heart beat count precision,
|
||||||
|
// or alternatively as expressed below.
|
||||||
|
*last_update += frequency.mul_f32(*heartbeats as f32);
|
||||||
|
// Note: we want to preserve incomplete heartbeats, and include them
|
||||||
|
// in the next update.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the number of times this duration has elasped since the last
|
||||||
|
/// tick:
|
||||||
|
/// - if it's more frequent then tick rate, it could be 1 or more.
|
||||||
|
/// - if it's less frequent then tick rate, it could be 1 or 0.
|
||||||
|
/// - if it's equal to the tick rate, it could be between 2 and 0, due to
|
||||||
|
/// delta time variance.
|
||||||
|
pub fn heartbeats(&mut self, frequency: Duration) -> u8 {
|
||||||
|
self.timers
|
||||||
|
.entry(frequency)
|
||||||
|
.or_insert_with(|| (Instant::now(), 0))
|
||||||
|
.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) { self.timers.clear() }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Particle {
|
||||||
|
alive_until: Instant, // created_at + lifespan
|
||||||
|
instance: ParticleInstance,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Particle {
|
||||||
|
fn new(lifespan: Duration, time: f64, mode: ParticleMode, pos: Vec3<f32>) -> Self {
|
||||||
|
Particle {
|
||||||
|
alive_until: Instant::now() + lifespan,
|
||||||
|
instance: ParticleInstance::new(time, mode, pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -171,6 +171,7 @@ impl Scene {
|
|||||||
view_mat,
|
view_mat,
|
||||||
proj_mat,
|
proj_mat,
|
||||||
cam_pos,
|
cam_pos,
|
||||||
|
..
|
||||||
} = self.camera.dependents();
|
} = self.camera.dependents();
|
||||||
const VD: f32 = 115.0; // View Distance
|
const VD: f32 = 115.0; // View Distance
|
||||||
const TIME: f64 = 43200.0; // 12 hours*3600 seconds
|
const TIME: f64 = 43200.0; // 12 hours*3600 seconds
|
||||||
|
@ -21,6 +21,7 @@ use common::{
|
|||||||
},
|
},
|
||||||
event::EventBus,
|
event::EventBus,
|
||||||
msg::ClientState,
|
msg::ClientState,
|
||||||
|
outcome::Outcome,
|
||||||
terrain::{Block, BlockKind},
|
terrain::{Block, BlockKind},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
@ -100,7 +101,12 @@ impl SessionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Tick the session (and the client attached to it).
|
/// Tick the session (and the client attached to it).
|
||||||
fn tick(&mut self, dt: Duration, global_state: &mut GlobalState) -> Result<TickAction, Error> {
|
fn tick(
|
||||||
|
&mut self,
|
||||||
|
dt: Duration,
|
||||||
|
global_state: &mut GlobalState,
|
||||||
|
outcomes: &mut Vec<Outcome>,
|
||||||
|
) -> Result<TickAction, Error> {
|
||||||
self.inputs.tick(dt);
|
self.inputs.tick(dt);
|
||||||
|
|
||||||
let mut client = self.client.borrow_mut();
|
let mut client = self.client.borrow_mut();
|
||||||
@ -158,6 +164,7 @@ impl SessionState {
|
|||||||
global_state.settings.graphics.view_distance = vd;
|
global_state.settings.graphics.view_distance = vd;
|
||||||
global_state.settings.save_to_file_warn();
|
global_state.settings.save_to_file_warn();
|
||||||
},
|
},
|
||||||
|
client::Event::Outcome(outcome) => outcomes.push(outcome),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +216,7 @@ impl PlayState for SessionState {
|
|||||||
.camera_mut()
|
.camera_mut()
|
||||||
.compute_dependents(&*self.client.borrow().state().terrain());
|
.compute_dependents(&*self.client.borrow().state().terrain());
|
||||||
let camera::Dependents {
|
let camera::Dependents {
|
||||||
view_mat, cam_pos, ..
|
cam_pos, cam_dir, ..
|
||||||
} = self.scene.camera().dependents();
|
} = self.scene.camera().dependents();
|
||||||
|
|
||||||
let (is_aiming, aim_dir_offset) = {
|
let (is_aiming, aim_dir_offset) = {
|
||||||
@ -232,8 +239,6 @@ impl PlayState for SessionState {
|
|||||||
};
|
};
|
||||||
self.is_aiming = is_aiming;
|
self.is_aiming = is_aiming;
|
||||||
|
|
||||||
let cam_dir: Vec3<f32> = Vec3::from(view_mat.inverted() * -Vec4::unit_z());
|
|
||||||
|
|
||||||
// Check to see whether we're aiming at anything
|
// Check to see whether we're aiming at anything
|
||||||
let (build_pos, select_pos, target_entity) =
|
let (build_pos, select_pos, target_entity) =
|
||||||
under_cursor(&self.client.borrow(), cam_pos, cam_dir);
|
under_cursor(&self.client.borrow(), cam_pos, cam_dir);
|
||||||
@ -645,10 +650,16 @@ impl PlayState for SessionState {
|
|||||||
|
|
||||||
self.inputs.climb = self.key_state.climb();
|
self.inputs.climb = self.key_state.climb();
|
||||||
|
|
||||||
|
let mut outcomes = Vec::new();
|
||||||
|
|
||||||
// Runs if either in a multiplayer server or the singleplayer server is unpaused
|
// Runs if either in a multiplayer server or the singleplayer server is unpaused
|
||||||
if !global_state.paused() {
|
if !global_state.paused() {
|
||||||
// Perform an in-game tick.
|
// Perform an in-game tick.
|
||||||
match self.tick(global_state.clock.get_avg_delta(), global_state) {
|
match self.tick(
|
||||||
|
global_state.clock.get_avg_delta(),
|
||||||
|
global_state,
|
||||||
|
&mut outcomes,
|
||||||
|
) {
|
||||||
Ok(TickAction::Continue) => {}, // Do nothing
|
Ok(TickAction::Continue) => {}, // Do nothing
|
||||||
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
|
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -701,6 +712,9 @@ impl PlayState for SessionState {
|
|||||||
num_visible_chunks: self.scene.terrain().visible_chunk_count() as u32,
|
num_visible_chunks: self.scene.terrain().visible_chunk_count() as u32,
|
||||||
num_figures: self.scene.figure_mgr().figure_count() as u32,
|
num_figures: self.scene.figure_mgr().figure_count() as u32,
|
||||||
num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32,
|
num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32,
|
||||||
|
num_particles: self.scene.particle_mgr().particle_count() as u32,
|
||||||
|
num_particles_visible: self.scene.particle_mgr().particle_count_visible()
|
||||||
|
as u32,
|
||||||
},
|
},
|
||||||
&self.scene.camera(),
|
&self.scene.camera(),
|
||||||
global_state.clock.get_last_delta(),
|
global_state.clock.get_last_delta(),
|
||||||
@ -946,6 +960,10 @@ impl PlayState for SessionState {
|
|||||||
self.voxygen_i18n.log_missing_entries();
|
self.voxygen_i18n.log_missing_entries();
|
||||||
self.hud.update_language(self.voxygen_i18n.clone());
|
self.hud.update_language(self.voxygen_i18n.clone());
|
||||||
},
|
},
|
||||||
|
HudEvent::ToggleParticlesEnabled(particles_enabled) => {
|
||||||
|
global_state.settings.graphics.particles_enabled = particles_enabled;
|
||||||
|
global_state.settings.save_to_file_warn();
|
||||||
|
},
|
||||||
HudEvent::ToggleFullscreen => {
|
HudEvent::ToggleFullscreen => {
|
||||||
global_state
|
global_state
|
||||||
.window
|
.window
|
||||||
@ -1010,6 +1028,7 @@ impl PlayState for SessionState {
|
|||||||
mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable,
|
mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable,
|
||||||
sprite_render_distance: global_state.settings.graphics.sprite_render_distance
|
sprite_render_distance: global_state.settings.graphics.sprite_render_distance
|
||||||
as f32,
|
as f32,
|
||||||
|
particles_enabled: global_state.settings.graphics.particles_enabled,
|
||||||
figure_lod_render_distance: global_state
|
figure_lod_render_distance: global_state
|
||||||
.settings
|
.settings
|
||||||
.graphics
|
.graphics
|
||||||
@ -1025,6 +1044,12 @@ impl PlayState for SessionState {
|
|||||||
&mut global_state.audio,
|
&mut global_state.audio,
|
||||||
&scene_data,
|
&scene_data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Process outcomes from client
|
||||||
|
for outcome in outcomes {
|
||||||
|
self.scene
|
||||||
|
.handle_outcome(&outcome, &scene_data, &mut global_state.audio);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1065,6 +1090,7 @@ impl PlayState for SessionState {
|
|||||||
mouse_smoothing: settings.gameplay.smooth_pan_enable,
|
mouse_smoothing: settings.gameplay.smooth_pan_enable,
|
||||||
sprite_render_distance: settings.graphics.sprite_render_distance as f32,
|
sprite_render_distance: settings.graphics.sprite_render_distance as f32,
|
||||||
figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32,
|
figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32,
|
||||||
|
particles_enabled: settings.graphics.particles_enabled,
|
||||||
is_aiming: self.is_aiming,
|
is_aiming: self.is_aiming,
|
||||||
};
|
};
|
||||||
self.scene.render(
|
self.scene.render(
|
||||||
|
@ -611,6 +611,7 @@ impl Default for Log {
|
|||||||
pub struct GraphicsSettings {
|
pub struct GraphicsSettings {
|
||||||
pub view_distance: u32,
|
pub view_distance: u32,
|
||||||
pub sprite_render_distance: u32,
|
pub sprite_render_distance: u32,
|
||||||
|
pub particles_enabled: bool,
|
||||||
pub figure_lod_render_distance: u32,
|
pub figure_lod_render_distance: u32,
|
||||||
pub max_fps: u32,
|
pub max_fps: u32,
|
||||||
pub fov: u16,
|
pub fov: u16,
|
||||||
@ -627,6 +628,7 @@ impl Default for GraphicsSettings {
|
|||||||
Self {
|
Self {
|
||||||
view_distance: 10,
|
view_distance: 10,
|
||||||
sprite_render_distance: 150,
|
sprite_render_distance: 150,
|
||||||
|
particles_enabled: true,
|
||||||
figure_lod_render_distance: 250,
|
figure_lod_render_distance: 250,
|
||||||
max_fps: 60,
|
max_fps: 60,
|
||||||
fov: 50,
|
fov: 50,
|
||||||
|
Loading…
Reference in New Issue
Block a user