mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Improved archery with feedback sfx and particles
This commit is contained in:
parent
88f99986af
commit
b0acbda236
@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Generated a net world map
|
- Generated a net world map
|
||||||
- Overhauled clouds for more verticality and performance
|
- Overhauled clouds for more verticality and performance
|
||||||
- New tooltip for items with stats comparison
|
- New tooltip for items with stats comparison
|
||||||
|
- Improved bow feedback, added arrow particles
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
BIN
assets/voxygen/audio/sfx/arrow_hit.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/arrow_hit.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/arrow_miss.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/arrow_miss.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -62,6 +62,7 @@ const int EXPLOSION = 20;
|
|||||||
const int ICE = 21;
|
const int ICE = 21;
|
||||||
const int LIFESTEAL_BEAM = 22;
|
const int LIFESTEAL_BEAM = 22;
|
||||||
const int CULTIST_FLAME = 23;
|
const int CULTIST_FLAME = 23;
|
||||||
|
const int STATIC_SMOKE = 24;
|
||||||
|
|
||||||
// meters per second squared (acceleration)
|
// meters per second squared (acceleration)
|
||||||
const float earth_gravity = 9.807;
|
const float earth_gravity = 9.807;
|
||||||
@ -402,6 +403,13 @@ void main() {
|
|||||||
vec4(purp_color, 0.0, purp_color, 1),
|
vec4(purp_color, 0.0, purp_color, 1),
|
||||||
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
||||||
);
|
);
|
||||||
|
} else if (inst_mode == STATIC_SMOKE) {
|
||||||
|
attr = Attr(
|
||||||
|
vec3(0),
|
||||||
|
vec3((0.5 * (1 - slow_start(0.8)))),
|
||||||
|
vec4(1.0),
|
||||||
|
spin_in_axis(vec3(rand6, rand7, rand8), rand9)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
attr = Attr(
|
attr = Attr(
|
||||||
linear_motion(
|
linear_motion(
|
||||||
@ -424,7 +432,7 @@ void main() {
|
|||||||
vec4 normals[6] = vec4[](vec4(-1,0,0,0), vec4(1,0,0,0), vec4(0,-1,0,0), vec4(0,1,0,0), vec4(0,0,-1,0), vec4(0,0,1,0));
|
vec4 normals[6] = vec4[](vec4(-1,0,0,0), vec4(1,0,0,0), vec4(0,-1,0,0), vec4(0,1,0,0), vec4(0,0,-1,0), vec4(0,0,1,0));
|
||||||
f_norm =
|
f_norm =
|
||||||
// inst_pos *
|
// inst_pos *
|
||||||
((normals[(v_norm_ao >> 0) & 0x7u]) * attr.rot).xyz;
|
normalize(((normals[(v_norm_ao >> 0) & 0x7u]) * attr.rot).xyz);
|
||||||
|
|
||||||
//vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0;
|
//vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0;
|
||||||
f_col = vec4(attr.col.rgb, attr.col.a);
|
f_col = vec4(attr.col.rgb, attr.col.a);
|
||||||
|
@ -1033,14 +1033,15 @@ impl Client {
|
|||||||
|
|
||||||
pub fn loaded_distance(&self) -> f32 { self.loaded_distance }
|
pub fn loaded_distance(&self) -> f32 { self.loaded_distance }
|
||||||
|
|
||||||
pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
|
pub fn position(&self) -> Option<Vec3<f32>> {
|
||||||
let chunk_pos = Vec2::from(
|
|
||||||
self.state
|
self.state
|
||||||
.read_storage::<comp::Pos>()
|
.read_storage::<comp::Pos>()
|
||||||
.get(self.entity())
|
.get(self.entity())
|
||||||
.cloned()?
|
.map(|v| v.0)
|
||||||
.0,
|
}
|
||||||
)
|
|
||||||
|
pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
|
||||||
|
let chunk_pos = Vec2::from(self.position()?)
|
||||||
.map2(TerrainChunkSize::RECT_SIZE, |e: f32, sz| {
|
.map2(TerrainChunkSize::RECT_SIZE, |e: f32, sz| {
|
||||||
(e as u32).div_euclid(sz) as i32
|
(e as u32).div_euclid(sz) as i32
|
||||||
});
|
});
|
||||||
|
@ -8,13 +8,25 @@ use specs_idvs::IdvStorage;
|
|||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use vek::ops::{Lerp, Slerp};
|
use vek::ops::{Lerp, Slerp};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct InterpBuffer<T> {
|
pub struct InterpBuffer<T> {
|
||||||
pub buf: [(f64, T); 4],
|
pub buf: [(f64, T); 4],
|
||||||
pub i: usize,
|
pub i: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> InterpBuffer<T> {
|
impl<T: Clone> InterpBuffer<T> {
|
||||||
|
pub fn new(x: T) -> Self {
|
||||||
|
Self {
|
||||||
|
buf: [
|
||||||
|
(0.0, x.clone()),
|
||||||
|
(0.0, x.clone()),
|
||||||
|
(0.0, x.clone()),
|
||||||
|
(0.0, x),
|
||||||
|
],
|
||||||
|
i: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn push(&mut self, time: f64, x: T) {
|
fn push(&mut self, time: f64, x: T) {
|
||||||
let InterpBuffer {
|
let InterpBuffer {
|
||||||
ref mut buf,
|
ref mut buf,
|
||||||
@ -54,6 +66,8 @@ impl InterpolatableComponent for Pos {
|
|||||||
type InterpData = InterpBuffer<Pos>;
|
type InterpData = InterpBuffer<Pos>;
|
||||||
type ReadData = InterpBuffer<Vel>;
|
type ReadData = InterpBuffer<Vel>;
|
||||||
|
|
||||||
|
fn new_data(x: Self) -> Self::InterpData { InterpBuffer::new(x) }
|
||||||
|
|
||||||
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64, force_update: bool) {
|
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64, force_update: bool) {
|
||||||
interp_data.update(time, *self, force_update);
|
interp_data.update(time, *self, force_update);
|
||||||
}
|
}
|
||||||
@ -108,6 +122,8 @@ impl InterpolatableComponent for Vel {
|
|||||||
type InterpData = InterpBuffer<Vel>;
|
type InterpData = InterpBuffer<Vel>;
|
||||||
type ReadData = ();
|
type ReadData = ();
|
||||||
|
|
||||||
|
fn new_data(x: Self) -> Self::InterpData { InterpBuffer::new(x) }
|
||||||
|
|
||||||
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64, force_update: bool) {
|
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64, force_update: bool) {
|
||||||
interp_data.update(time, *self, force_update);
|
interp_data.update(time, *self, force_update);
|
||||||
}
|
}
|
||||||
@ -143,6 +159,8 @@ impl InterpolatableComponent for Ori {
|
|||||||
type InterpData = InterpBuffer<Ori>;
|
type InterpData = InterpBuffer<Ori>;
|
||||||
type ReadData = ();
|
type ReadData = ();
|
||||||
|
|
||||||
|
fn new_data(x: Self) -> Self::InterpData { InterpBuffer::new(x) }
|
||||||
|
|
||||||
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64, force_update: bool) {
|
fn update_component(&self, interp_data: &mut Self::InterpData, time: f64, force_update: bool) {
|
||||||
interp_data.update(time, *self, force_update);
|
interp_data.update(time, *self, force_update);
|
||||||
}
|
}
|
||||||
|
@ -47,20 +47,21 @@ pub fn handle_remove<C: Component>(entity: Entity, world: &World) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait InterpolatableComponent: Component {
|
pub trait InterpolatableComponent: Component {
|
||||||
type InterpData: Component + Default;
|
type InterpData: Component;
|
||||||
type ReadData;
|
type ReadData;
|
||||||
|
|
||||||
|
fn new_data(x: Self) -> Self::InterpData;
|
||||||
fn update_component(&self, data: &mut Self::InterpData, time: f64, force_update: bool);
|
fn update_component(&self, data: &mut Self::InterpData, time: f64, force_update: bool);
|
||||||
fn interpolate(self, data: &Self::InterpData, time: f64, read_data: &Self::ReadData) -> Self;
|
fn interpolate(self, data: &Self::InterpData, time: f64, read_data: &Self::ReadData) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_interp_insert<C: InterpolatableComponent>(
|
pub fn handle_interp_insert<C: InterpolatableComponent + Clone>(
|
||||||
comp: C,
|
comp: C,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
world: &World,
|
world: &World,
|
||||||
force_update: bool,
|
force_update: bool,
|
||||||
) {
|
) {
|
||||||
let mut interp_data = C::InterpData::default();
|
let mut interp_data = C::new_data(comp.clone());
|
||||||
let time = world.read_resource::<Time>().0;
|
let time = world.read_resource::<Time>().0;
|
||||||
comp.update_component(&mut interp_data, time, force_update);
|
comp.update_component(&mut interp_data, time, force_update);
|
||||||
handle_insert(comp, entity, world);
|
handle_insert(comp, entity, world);
|
||||||
|
@ -22,6 +22,13 @@ pub enum Outcome {
|
|||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
vel: Vec3<f32>,
|
vel: Vec3<f32>,
|
||||||
},
|
},
|
||||||
|
ProjectileHit {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
body: comp::Body,
|
||||||
|
vel: Vec3<f32>,
|
||||||
|
source: Option<Uid>,
|
||||||
|
target: Option<Uid>,
|
||||||
|
},
|
||||||
Beam {
|
Beam {
|
||||||
pos: Vec3<f32>,
|
pos: Vec3<f32>,
|
||||||
specifier: beam::FrontendSpecifier,
|
specifier: beam::FrontendSpecifier,
|
||||||
@ -56,6 +63,7 @@ impl Outcome {
|
|||||||
match self {
|
match self {
|
||||||
Outcome::Explosion { pos, .. }
|
Outcome::Explosion { pos, .. }
|
||||||
| Outcome::ProjectileShot { pos, .. }
|
| Outcome::ProjectileShot { pos, .. }
|
||||||
|
| Outcome::ProjectileHit { pos, .. }
|
||||||
| Outcome::Beam { pos, .. }
|
| Outcome::Beam { pos, .. }
|
||||||
| Outcome::SkillPointGain { pos, .. }
|
| Outcome::SkillPointGain { pos, .. }
|
||||||
| Outcome::SummonedCreature { pos, .. } => Some(*pos),
|
| Outcome::SummonedCreature { pos, .. } => Some(*pos),
|
||||||
|
@ -10,6 +10,7 @@ use common::{
|
|||||||
},
|
},
|
||||||
consts::{FRIC_GROUND, GRAVITY},
|
consts::{FRIC_GROUND, GRAVITY},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
|
outcome::Outcome,
|
||||||
resources::DeltaTime,
|
resources::DeltaTime,
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
@ -20,7 +21,7 @@ use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
|
|||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use specs::{
|
use specs::{
|
||||||
shred::{ResourceId, World},
|
shred::{ResourceId, World},
|
||||||
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, WriteExpect,
|
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect,
|
||||||
WriteStorage,
|
WriteStorage,
|
||||||
};
|
};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
@ -106,6 +107,7 @@ pub struct PhysicsWrite<'a> {
|
|||||||
pos_vel_defers: WriteStorage<'a, PosVelDefer>,
|
pos_vel_defers: WriteStorage<'a, PosVelDefer>,
|
||||||
orientations: WriteStorage<'a, Ori>,
|
orientations: WriteStorage<'a, Ori>,
|
||||||
previous_phys_cache: WriteStorage<'a, PreviousPhysCache>,
|
previous_phys_cache: WriteStorage<'a, PreviousPhysCache>,
|
||||||
|
outcomes: Write<'a, Vec<Outcome>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
@ -590,7 +592,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
let velocities = &write.velocities;
|
let velocities = &write.velocities;
|
||||||
|
|
||||||
// Second pass: resolve collisions
|
// Second pass: resolve collisions
|
||||||
let land_on_grounds = (
|
let (land_on_grounds, mut outcomes) = (
|
||||||
&read.entities,
|
&read.entities,
|
||||||
read.scales.maybe(),
|
read.scales.maybe(),
|
||||||
read.stickies.maybe(),
|
read.stickies.maybe(),
|
||||||
@ -628,16 +630,12 @@ impl<'a> PhysicsData<'a> {
|
|||||||
_,
|
_,
|
||||||
)| {
|
)| {
|
||||||
let mut land_on_ground = None;
|
let mut land_on_ground = None;
|
||||||
|
let mut outcomes = Vec::new();
|
||||||
// Defer the writes of positions and velocities to allow an inner loop over
|
// Defer the writes of positions and velocities to allow an inner loop over
|
||||||
// terrain-like entities
|
// terrain-like entities
|
||||||
let old_vel = *vel;
|
let old_vel = *vel;
|
||||||
let mut vel = *vel;
|
let mut vel = *vel;
|
||||||
|
|
||||||
if sticky.is_some() && physics_state.on_surface().is_some() {
|
|
||||||
vel.0 = physics_state.ground_vel;
|
|
||||||
return land_on_ground;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scale = if let Collider::Voxel { .. } = collider {
|
let scale = if let Collider::Voxel { .. } = collider {
|
||||||
scale.map(|s| s.0).unwrap_or(1.0)
|
scale.map(|s| s.0).unwrap_or(1.0)
|
||||||
} else {
|
} else {
|
||||||
@ -738,17 +736,45 @@ impl<'a> PhysicsData<'a> {
|
|||||||
Collider::Point => {
|
Collider::Point => {
|
||||||
let mut pos = *pos;
|
let mut pos = *pos;
|
||||||
|
|
||||||
|
let (dist, block) = if let Some(block) = read
|
||||||
|
.terrain
|
||||||
|
.get(pos.0.map(|e| e.floor() as i32))
|
||||||
|
.ok()
|
||||||
|
.filter(|b| b.is_filled())
|
||||||
|
// TODO: `is_solid`, when arrows are special-cased
|
||||||
|
{
|
||||||
|
(0.0, Some(block))
|
||||||
|
} else {
|
||||||
let (dist, block) = read
|
let (dist, block) = read
|
||||||
.terrain
|
.terrain
|
||||||
.ray(pos.0, pos.0 + pos_delta)
|
.ray(pos.0, pos.0 + pos_delta)
|
||||||
.until(|block: &Block| block.is_filled())
|
.until(|block: &Block| block.is_filled())
|
||||||
.ignore_error()
|
.ignore_error()
|
||||||
.cast();
|
.cast();
|
||||||
|
(dist, block.unwrap()) // Can't fail since we do ignore_error above
|
||||||
|
};
|
||||||
|
|
||||||
pos.0 += pos_delta.try_normalized().unwrap_or_else(Vec3::zero) * dist;
|
pos.0 += pos_delta.try_normalized().unwrap_or_else(Vec3::zero) * dist;
|
||||||
|
|
||||||
// Can't fail since we do ignore_error above
|
// TODO: Not all projectiles should count as sticky!
|
||||||
if block.unwrap().is_some() {
|
if sticky.is_some() {
|
||||||
|
if let Some((projectile, body)) = read
|
||||||
|
.projectiles
|
||||||
|
.get(entity)
|
||||||
|
.filter(|_| vel.0.magnitude_squared() > 1.0 && block.is_some())
|
||||||
|
.zip(read.bodies.get(entity).copied())
|
||||||
|
{
|
||||||
|
outcomes.push(Outcome::ProjectileHit {
|
||||||
|
pos: pos.0,
|
||||||
|
body,
|
||||||
|
vel: vel.0,
|
||||||
|
source: projectile.owner,
|
||||||
|
target: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.is_some() {
|
||||||
let block_center = pos.0.map(|e| e.floor()) + 0.5;
|
let block_center = pos.0.map(|e| e.floor()) + 0.5;
|
||||||
let block_rpos = (pos.0 - block_center)
|
let block_rpos = (pos.0 - block_center)
|
||||||
.try_normalized()
|
.try_normalized()
|
||||||
@ -774,6 +800,11 @@ impl<'a> PhysicsData<'a> {
|
|||||||
Vec3::unit_y() * -block_rpos.y.signum()
|
Vec3::unit_y() * -block_rpos.y.signum()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sticky things shouldn't move
|
||||||
|
if sticky.is_some() {
|
||||||
|
vel.0 = Vec3::zero();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
physics_state.in_liquid = read
|
physics_state.in_liquid = read
|
||||||
@ -978,20 +1009,31 @@ impl<'a> PhysicsData<'a> {
|
|||||||
pos_vel_defer.vel = None;
|
pos_vel_defer.vel = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
land_on_ground
|
(land_on_ground, outcomes)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.fold(Vec::new, |mut land_on_grounds, land_on_ground| {
|
.fold(
|
||||||
|
|| (Vec::new(), Vec::new()),
|
||||||
|
|(mut land_on_grounds, mut all_outcomes), (land_on_ground, mut outcomes)| {
|
||||||
land_on_ground.map(|log| land_on_grounds.push(log));
|
land_on_ground.map(|log| land_on_grounds.push(log));
|
||||||
land_on_grounds
|
all_outcomes.append(&mut outcomes);
|
||||||
})
|
(land_on_grounds, all_outcomes)
|
||||||
.reduce(Vec::new, |mut land_on_grounds_a, mut land_on_grounds_b| {
|
},
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
|| (Vec::new(), Vec::new()),
|
||||||
|
|(mut land_on_grounds_a, mut outcomes_a),
|
||||||
|
(mut land_on_grounds_b, mut outcomes_b)| {
|
||||||
land_on_grounds_a.append(&mut land_on_grounds_b);
|
land_on_grounds_a.append(&mut land_on_grounds_b);
|
||||||
land_on_grounds_a
|
outcomes_a.append(&mut outcomes_b);
|
||||||
});
|
(land_on_grounds_a, outcomes_a)
|
||||||
|
},
|
||||||
|
);
|
||||||
drop(guard);
|
drop(guard);
|
||||||
job.cpu_stats.measure(ParMode::Single);
|
job.cpu_stats.measure(ParMode::Single);
|
||||||
|
|
||||||
|
write.outcomes.append(&mut outcomes);
|
||||||
|
|
||||||
prof_span!(guard, "write deferred pos and vel");
|
prof_span!(guard, "write deferred pos and vel");
|
||||||
for (_, pos, vel, pos_vel_defer) in (
|
for (_, pos, vel, pos_vel_defer) in (
|
||||||
&read.entities,
|
&read.entities,
|
||||||
@ -1279,6 +1321,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
|||||||
if d * e.signum() < 0.0 { 0.0 } else { e }
|
if d * e.signum() < 0.0 { 0.0 } else { e }
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 });
|
pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
use common::{
|
use common::{
|
||||||
combat::{AttackerInfo, TargetInfo},
|
combat::{AttackerInfo, TargetInfo},
|
||||||
comp::{
|
comp::{
|
||||||
projectile, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Pos,
|
projectile, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState,
|
||||||
Projectile, Stats, Vel,
|
Pos, Projectile, Stats, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
|
outcome::Outcome,
|
||||||
resources::DeltaTime,
|
resources::DeltaTime,
|
||||||
uid::UidAllocator,
|
uid::{Uid, UidAllocator},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
GroupTarget,
|
GroupTarget,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||||
World, WriteStorage,
|
World, Write, WriteStorage,
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
pub struct ReadData<'a> {
|
pub struct ReadData<'a> {
|
||||||
@ -23,6 +25,7 @@ pub struct ReadData<'a> {
|
|||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||||
|
uids: ReadStorage<'a, Uid>,
|
||||||
positions: ReadStorage<'a, Pos>,
|
positions: ReadStorage<'a, Pos>,
|
||||||
physics_states: ReadStorage<'a, PhysicsState>,
|
physics_states: ReadStorage<'a, PhysicsState>,
|
||||||
velocities: ReadStorage<'a, Vel>,
|
velocities: ReadStorage<'a, Vel>,
|
||||||
@ -32,6 +35,7 @@ pub struct ReadData<'a> {
|
|||||||
stats: ReadStorage<'a, Stats>,
|
stats: ReadStorage<'a, Stats>,
|
||||||
combos: ReadStorage<'a, Combo>,
|
combos: ReadStorage<'a, Combo>,
|
||||||
healths: ReadStorage<'a, Health>,
|
healths: ReadStorage<'a, Health>,
|
||||||
|
bodies: ReadStorage<'a, Body>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This system is responsible for handling projectile effect triggers
|
/// This system is responsible for handling projectile effect triggers
|
||||||
@ -42,13 +46,17 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadData<'a>,
|
ReadData<'a>,
|
||||||
WriteStorage<'a, Ori>,
|
WriteStorage<'a, Ori>,
|
||||||
WriteStorage<'a, Projectile>,
|
WriteStorage<'a, Projectile>,
|
||||||
|
Write<'a, Vec<Outcome>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const NAME: &'static str = "projectile";
|
const NAME: &'static str = "projectile";
|
||||||
const ORIGIN: Origin = Origin::Common;
|
const ORIGIN: Origin = Origin::Common;
|
||||||
const PHASE: Phase = Phase::Create;
|
const PHASE: Phase = Phase::Create;
|
||||||
|
|
||||||
fn run(_job: &mut Job<Self>, (read_data, mut orientations, mut projectiles): Self::SystemData) {
|
fn run(
|
||||||
|
_job: &mut Job<Self>,
|
||||||
|
(read_data, mut orientations, mut projectiles, mut outcomes): Self::SystemData,
|
||||||
|
) {
|
||||||
let mut server_emitter = read_data.server_bus.emitter();
|
let mut server_emitter = read_data.server_bus.emitter();
|
||||||
|
|
||||||
// Attacks
|
// Attacks
|
||||||
@ -123,6 +131,19 @@ impl<'a> System<'a> for Sys {
|
|||||||
health: read_data.healths.get(target),
|
health: read_data.healths.get(target),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(&body) = read_data.bodies.get(entity) {
|
||||||
|
outcomes.push(Outcome::ProjectileHit {
|
||||||
|
pos: pos.0,
|
||||||
|
body,
|
||||||
|
vel: read_data
|
||||||
|
.velocities
|
||||||
|
.get(entity)
|
||||||
|
.map_or(Vec3::zero(), |v| v.0),
|
||||||
|
source: projectile.owner,
|
||||||
|
target: read_data.uids.get(target).copied(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
attack.apply_attack(
|
attack.apply_attack(
|
||||||
target_group,
|
target_group,
|
||||||
attacker_info,
|
attacker_info,
|
||||||
|
@ -289,7 +289,12 @@ impl SfxMgr {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_outcome(&mut self, outcome: &Outcome, audio: &mut AudioFrontend) {
|
pub fn handle_outcome(
|
||||||
|
&mut self,
|
||||||
|
outcome: &Outcome,
|
||||||
|
audio: &mut AudioFrontend,
|
||||||
|
client: &Client,
|
||||||
|
) {
|
||||||
if !audio.sfx_enabled() {
|
if !audio.sfx_enabled() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -350,6 +355,33 @@ impl SfxMgr {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Outcome::ProjectileHit {
|
||||||
|
pos,
|
||||||
|
body,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
..
|
||||||
|
} => match body {
|
||||||
|
Body::Object(
|
||||||
|
object::Body::Arrow
|
||||||
|
| object::Body::MultiArrow
|
||||||
|
| object::Body::ArrowSnake
|
||||||
|
| object::Body::ArrowTurret,
|
||||||
|
) => {
|
||||||
|
if target.is_none() {
|
||||||
|
audio.play_sfx("voxygen.audio.sfx.arrow_miss", *pos, Some(2.0));
|
||||||
|
} else if *source == client.uid() {
|
||||||
|
audio.play_sfx(
|
||||||
|
"voxygen.audio.sfx.arrow_hit",
|
||||||
|
client.position().unwrap_or(*pos),
|
||||||
|
Some(2.0),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
audio.play_sfx("voxygen.audio.sfx.arrow_hit", *pos, Some(2.0));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
},
|
||||||
Outcome::SkillPointGain { pos, .. } => {
|
Outcome::SkillPointGain { pos, .. } => {
|
||||||
let file_ref = "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up";
|
let file_ref = "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up";
|
||||||
audio.play_sfx(file_ref, *pos, None);
|
audio.play_sfx(file_ref, *pos, None);
|
||||||
|
@ -41,8 +41,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
// Update interpolation values
|
// Update interpolation values, but don't interpolate far things or objects
|
||||||
if i.pos.distance_squared(pos.0) < 64.0 * 64.0 {
|
if i.pos.distance_squared(pos.0) < 64.0 * 64.0 && !matches!(body, Body::Object(_)) {
|
||||||
i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, 10.0 * dt.0);
|
i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, 10.0 * dt.0);
|
||||||
i.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0);
|
i.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0);
|
||||||
} else {
|
} else {
|
||||||
|
@ -120,6 +120,7 @@ pub enum ParticleMode {
|
|||||||
Ice = 21,
|
Ice = 21,
|
||||||
LifestealBeam = 22,
|
LifestealBeam = 22,
|
||||||
CultistFlame = 23,
|
CultistFlame = 23,
|
||||||
|
StaticSmoke = 24,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticleMode {
|
impl ParticleMode {
|
||||||
|
@ -108,6 +108,7 @@ pub struct Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct SceneData<'a> {
|
pub struct SceneData<'a> {
|
||||||
|
pub client: &'a Client,
|
||||||
pub state: &'a State,
|
pub state: &'a State,
|
||||||
pub player_entity: specs::Entity,
|
pub player_entity: specs::Entity,
|
||||||
pub target_entity: Option<specs::Entity>,
|
pub target_entity: Option<specs::Entity>,
|
||||||
@ -398,7 +399,8 @@ impl Scene {
|
|||||||
) {
|
) {
|
||||||
span!(_guard, "handle_outcome", "Scene::handle_outcome");
|
span!(_guard, "handle_outcome", "Scene::handle_outcome");
|
||||||
self.particle_mgr.handle_outcome(&outcome, &scene_data);
|
self.particle_mgr.handle_outcome(&outcome, &scene_data);
|
||||||
self.sfx_mgr.handle_outcome(&outcome, audio);
|
self.sfx_mgr
|
||||||
|
.handle_outcome(&outcome, audio, scene_data.client);
|
||||||
|
|
||||||
match outcome {
|
match outcome {
|
||||||
Outcome::Explosion {
|
Outcome::Explosion {
|
||||||
|
@ -196,6 +196,18 @@ impl ParticleMgr {
|
|||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
},
|
},
|
||||||
|
Outcome::ProjectileHit { pos, target, .. } => {
|
||||||
|
if target.is_some() {
|
||||||
|
self.particles.resize_with(self.particles.len() + 30, || {
|
||||||
|
Particle::new(
|
||||||
|
Duration::from_millis(100),
|
||||||
|
time,
|
||||||
|
ParticleMode::Shrapnel,
|
||||||
|
*pos,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
Outcome::ProjectileShot { .. }
|
Outcome::ProjectileShot { .. }
|
||||||
| Outcome::Beam { .. }
|
| Outcome::Beam { .. }
|
||||||
| Outcome::ExpChange { .. }
|
| Outcome::ExpChange { .. }
|
||||||
@ -257,6 +269,12 @@ impl ParticleMgr {
|
|||||||
Body::Object(object::Body::CampfireLit) => {
|
Body::Object(object::Body::CampfireLit) => {
|
||||||
self.maintain_campfirelit_particles(scene_data, pos, vel)
|
self.maintain_campfirelit_particles(scene_data, pos, vel)
|
||||||
},
|
},
|
||||||
|
Body::Object(
|
||||||
|
object::Body::Arrow
|
||||||
|
| object::Body::MultiArrow
|
||||||
|
| object::Body::ArrowSnake
|
||||||
|
| object::Body::ArrowTurret,
|
||||||
|
) => self.maintain_arrow_particles(scene_data, pos, vel),
|
||||||
Body::Object(object::Body::BoltFire) => {
|
Body::Object(object::Body::BoltFire) => {
|
||||||
self.maintain_boltfire_particles(scene_data, pos, vel)
|
self.maintain_boltfire_particles(scene_data, pos, vel)
|
||||||
},
|
},
|
||||||
@ -313,6 +331,33 @@ impl ParticleMgr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maintain_arrow_particles(&mut self, scene_data: &SceneData, pos: &Pos, vel: Option<&Vel>) {
|
||||||
|
const MIN_SPEED: f32 = 15.0;
|
||||||
|
// Don't emit particles for immobile arrows
|
||||||
|
if vel.map_or(true, |v| v.0.magnitude_squared() < MIN_SPEED.powi(2)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
span!(
|
||||||
|
_guard,
|
||||||
|
"arrow_particles",
|
||||||
|
"ParticleMgr::maintain_arrow_particles"
|
||||||
|
);
|
||||||
|
let time = scene_data.state.get_time();
|
||||||
|
let dt = scene_data.state.get_delta_time();
|
||||||
|
|
||||||
|
let count = self.scheduler.heartbeats(Duration::from_millis(2));
|
||||||
|
for i in 0..count {
|
||||||
|
let proportion = i as f32 / count as f32;
|
||||||
|
self.particles.push(Particle::new(
|
||||||
|
Duration::from_millis(200),
|
||||||
|
time,
|
||||||
|
ParticleMode::StaticSmoke,
|
||||||
|
pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * proportion),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn maintain_boltfire_particles(
|
fn maintain_boltfire_particles(
|
||||||
&mut self,
|
&mut self,
|
||||||
scene_data: &SceneData,
|
scene_data: &SceneData,
|
||||||
|
@ -1510,6 +1510,7 @@ impl PlayState for SessionState {
|
|||||||
{
|
{
|
||||||
let client = self.client.borrow();
|
let client = self.client.borrow();
|
||||||
let scene_data = SceneData {
|
let scene_data = SceneData {
|
||||||
|
client: &client,
|
||||||
state: client.state(),
|
state: client.state(),
|
||||||
player_entity: client.entity(),
|
player_entity: client.entity(),
|
||||||
// Only highlight if interactable
|
// Only highlight if interactable
|
||||||
@ -1577,6 +1578,7 @@ impl PlayState for SessionState {
|
|||||||
let client = self.client.borrow();
|
let client = self.client.borrow();
|
||||||
|
|
||||||
let scene_data = SceneData {
|
let scene_data = SceneData {
|
||||||
|
client: &client,
|
||||||
state: client.state(),
|
state: client.state(),
|
||||||
player_entity: client.entity(),
|
player_entity: client.entity(),
|
||||||
// Only highlight if interactable
|
// Only highlight if interactable
|
||||||
|
Loading…
Reference in New Issue
Block a user