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
|
||||
- Overhauled clouds for more verticality and performance
|
||||
- New tooltip for items with stats comparison
|
||||
- Improved bow feedback, added arrow particles
|
||||
|
||||
### 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 LIFESTEAL_BEAM = 22;
|
||||
const int CULTIST_FLAME = 23;
|
||||
const int STATIC_SMOKE = 24;
|
||||
|
||||
// meters per second squared (acceleration)
|
||||
const float earth_gravity = 9.807;
|
||||
@ -402,6 +403,13 @@ void main() {
|
||||
vec4(purp_color, 0.0, purp_color, 1),
|
||||
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 {
|
||||
attr = Attr(
|
||||
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));
|
||||
f_norm =
|
||||
// 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;
|
||||
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 current_chunk(&self) -> Option<Arc<TerrainChunk>> {
|
||||
let chunk_pos = Vec2::from(
|
||||
pub fn position(&self) -> Option<Vec3<f32>> {
|
||||
self.state
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(self.entity())
|
||||
.cloned()?
|
||||
.0,
|
||||
)
|
||||
.map(|v| v.0)
|
||||
}
|
||||
|
||||
pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
|
||||
let chunk_pos = Vec2::from(self.position()?)
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |e: f32, sz| {
|
||||
(e as u32).div_euclid(sz) as i32
|
||||
});
|
||||
|
@ -8,13 +8,25 @@ use specs_idvs::IdvStorage;
|
||||
use tracing::warn;
|
||||
use vek::ops::{Lerp, Slerp};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct InterpBuffer<T> {
|
||||
pub buf: [(f64, T); 4],
|
||||
pub i: usize,
|
||||
}
|
||||
|
||||
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) {
|
||||
let InterpBuffer {
|
||||
ref mut buf,
|
||||
@ -54,6 +66,8 @@ impl InterpolatableComponent for Pos {
|
||||
type InterpData = InterpBuffer<Pos>;
|
||||
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) {
|
||||
interp_data.update(time, *self, force_update);
|
||||
}
|
||||
@ -108,6 +122,8 @@ impl InterpolatableComponent for Vel {
|
||||
type InterpData = InterpBuffer<Vel>;
|
||||
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) {
|
||||
interp_data.update(time, *self, force_update);
|
||||
}
|
||||
@ -143,6 +159,8 @@ impl InterpolatableComponent for Ori {
|
||||
type InterpData = InterpBuffer<Ori>;
|
||||
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) {
|
||||
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 {
|
||||
type InterpData: Component + Default;
|
||||
type InterpData: Component;
|
||||
type ReadData;
|
||||
|
||||
fn new_data(x: Self) -> Self::InterpData;
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn handle_interp_insert<C: InterpolatableComponent>(
|
||||
pub fn handle_interp_insert<C: InterpolatableComponent + Clone>(
|
||||
comp: C,
|
||||
entity: Entity,
|
||||
world: &World,
|
||||
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;
|
||||
comp.update_component(&mut interp_data, time, force_update);
|
||||
handle_insert(comp, entity, world);
|
||||
|
@ -22,6 +22,13 @@ pub enum Outcome {
|
||||
body: comp::Body,
|
||||
vel: Vec3<f32>,
|
||||
},
|
||||
ProjectileHit {
|
||||
pos: Vec3<f32>,
|
||||
body: comp::Body,
|
||||
vel: Vec3<f32>,
|
||||
source: Option<Uid>,
|
||||
target: Option<Uid>,
|
||||
},
|
||||
Beam {
|
||||
pos: Vec3<f32>,
|
||||
specifier: beam::FrontendSpecifier,
|
||||
@ -56,6 +63,7 @@ impl Outcome {
|
||||
match self {
|
||||
Outcome::Explosion { pos, .. }
|
||||
| Outcome::ProjectileShot { pos, .. }
|
||||
| Outcome::ProjectileHit { pos, .. }
|
||||
| Outcome::Beam { pos, .. }
|
||||
| Outcome::SkillPointGain { pos, .. }
|
||||
| Outcome::SummonedCreature { pos, .. } => Some(*pos),
|
||||
|
@ -10,6 +10,7 @@ use common::{
|
||||
},
|
||||
consts::{FRIC_GROUND, GRAVITY},
|
||||
event::{EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
resources::DeltaTime,
|
||||
terrain::{Block, TerrainGrid},
|
||||
uid::Uid,
|
||||
@ -20,7 +21,7 @@ use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{
|
||||
shred::{ResourceId, World},
|
||||
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, WriteExpect,
|
||||
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect,
|
||||
WriteStorage,
|
||||
};
|
||||
use std::ops::Range;
|
||||
@ -106,6 +107,7 @@ pub struct PhysicsWrite<'a> {
|
||||
pos_vel_defers: WriteStorage<'a, PosVelDefer>,
|
||||
orientations: WriteStorage<'a, Ori>,
|
||||
previous_phys_cache: WriteStorage<'a, PreviousPhysCache>,
|
||||
outcomes: Write<'a, Vec<Outcome>>,
|
||||
}
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -590,7 +592,7 @@ impl<'a> PhysicsData<'a> {
|
||||
let velocities = &write.velocities;
|
||||
|
||||
// Second pass: resolve collisions
|
||||
let land_on_grounds = (
|
||||
let (land_on_grounds, mut outcomes) = (
|
||||
&read.entities,
|
||||
read.scales.maybe(),
|
||||
read.stickies.maybe(),
|
||||
@ -628,16 +630,12 @@ impl<'a> PhysicsData<'a> {
|
||||
_,
|
||||
)| {
|
||||
let mut land_on_ground = None;
|
||||
let mut outcomes = Vec::new();
|
||||
// Defer the writes of positions and velocities to allow an inner loop over
|
||||
// terrain-like entities
|
||||
let old_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 {
|
||||
scale.map(|s| s.0).unwrap_or(1.0)
|
||||
} else {
|
||||
@ -738,17 +736,45 @@ impl<'a> PhysicsData<'a> {
|
||||
Collider::Point => {
|
||||
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
|
||||
.terrain
|
||||
.ray(pos.0, pos.0 + pos_delta)
|
||||
.until(|block: &Block| block.is_filled())
|
||||
.ignore_error()
|
||||
.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;
|
||||
|
||||
// Can't fail since we do ignore_error above
|
||||
if block.unwrap().is_some() {
|
||||
// TODO: Not all projectiles should count as sticky!
|
||||
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_rpos = (pos.0 - block_center)
|
||||
.try_normalized()
|
||||
@ -774,6 +800,11 @@ impl<'a> PhysicsData<'a> {
|
||||
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
|
||||
@ -978,20 +1009,31 @@ impl<'a> PhysicsData<'a> {
|
||||
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_grounds
|
||||
})
|
||||
.reduce(Vec::new, |mut land_on_grounds_a, mut land_on_grounds_b| {
|
||||
all_outcomes.append(&mut outcomes);
|
||||
(land_on_grounds, all_outcomes)
|
||||
},
|
||||
)
|
||||
.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
|
||||
});
|
||||
outcomes_a.append(&mut outcomes_b);
|
||||
(land_on_grounds_a, outcomes_a)
|
||||
},
|
||||
);
|
||||
drop(guard);
|
||||
job.cpu_stats.measure(ParMode::Single);
|
||||
|
||||
write.outcomes.append(&mut outcomes);
|
||||
|
||||
prof_span!(guard, "write deferred pos and vel");
|
||||
for (_, pos, vel, pos_vel_defer) in (
|
||||
&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 }
|
||||
},
|
||||
);
|
||||
|
||||
pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 });
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,23 @@
|
||||
use common::{
|
||||
combat::{AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
projectile, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Pos,
|
||||
Projectile, Stats, Vel,
|
||||
projectile, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState,
|
||||
Pos, Projectile, Stats, Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
resources::DeltaTime,
|
||||
uid::UidAllocator,
|
||||
uid::{Uid, UidAllocator},
|
||||
util::Dir,
|
||||
GroupTarget,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
World, Write, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadData<'a> {
|
||||
@ -23,6 +25,7 @@ pub struct ReadData<'a> {
|
||||
dt: Read<'a, DeltaTime>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
physics_states: ReadStorage<'a, PhysicsState>,
|
||||
velocities: ReadStorage<'a, Vel>,
|
||||
@ -32,6 +35,7 @@ pub struct ReadData<'a> {
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
healths: ReadStorage<'a, Health>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling projectile effect triggers
|
||||
@ -42,13 +46,17 @@ impl<'a> System<'a> for Sys {
|
||||
ReadData<'a>,
|
||||
WriteStorage<'a, Ori>,
|
||||
WriteStorage<'a, Projectile>,
|
||||
Write<'a, Vec<Outcome>>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "projectile";
|
||||
const ORIGIN: Origin = Origin::Common;
|
||||
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();
|
||||
|
||||
// Attacks
|
||||
@ -123,6 +131,19 @@ impl<'a> System<'a> for Sys {
|
||||
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(
|
||||
target_group,
|
||||
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() {
|
||||
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, .. } => {
|
||||
let file_ref = "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
|
@ -41,8 +41,8 @@ impl<'a> System<'a> for Sys {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// Update interpolation values
|
||||
if i.pos.distance_squared(pos.0) < 64.0 * 64.0 {
|
||||
// Update interpolation values, but don't interpolate far things or objects
|
||||
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.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0);
|
||||
} else {
|
||||
|
@ -120,6 +120,7 @@ pub enum ParticleMode {
|
||||
Ice = 21,
|
||||
LifestealBeam = 22,
|
||||
CultistFlame = 23,
|
||||
StaticSmoke = 24,
|
||||
}
|
||||
|
||||
impl ParticleMode {
|
||||
|
@ -108,6 +108,7 @@ pub struct Scene {
|
||||
}
|
||||
|
||||
pub struct SceneData<'a> {
|
||||
pub client: &'a Client,
|
||||
pub state: &'a State,
|
||||
pub player_entity: specs::Entity,
|
||||
pub target_entity: Option<specs::Entity>,
|
||||
@ -398,7 +399,8 @@ impl Scene {
|
||||
) {
|
||||
span!(_guard, "handle_outcome", "Scene::handle_outcome");
|
||||
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 {
|
||||
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::Beam { .. }
|
||||
| Outcome::ExpChange { .. }
|
||||
@ -257,6 +269,12 @@ impl ParticleMgr {
|
||||
Body::Object(object::Body::CampfireLit) => {
|
||||
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) => {
|
||||
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(
|
||||
&mut self,
|
||||
scene_data: &SceneData,
|
||||
|
@ -1510,6 +1510,7 @@ impl PlayState for SessionState {
|
||||
{
|
||||
let client = self.client.borrow();
|
||||
let scene_data = SceneData {
|
||||
client: &client,
|
||||
state: client.state(),
|
||||
player_entity: client.entity(),
|
||||
// Only highlight if interactable
|
||||
@ -1577,6 +1578,7 @@ impl PlayState for SessionState {
|
||||
let client = self.client.borrow();
|
||||
|
||||
let scene_data = SceneData {
|
||||
client: &client,
|
||||
state: client.state(),
|
||||
player_entity: client.entity(),
|
||||
// Only highlight if interactable
|
||||
|
Loading…
Reference in New Issue
Block a user