diff --git a/assets/common/abilities/sceptre/healingbeam.ron b/assets/common/abilities/sceptre/healingbeam.ron index 930ad025d4..4fb8535507 100644 --- a/assets/common/abilities/sceptre/healingbeam.ron +++ b/assets/common/abilities/sceptre/healingbeam.ron @@ -8,4 +8,5 @@ HealingBeam( max_angle: 1.0, lifesteal_eff: 0.0, energy_cost: 50, + specifier: HealingBeam, ) \ No newline at end of file diff --git a/assets/common/abilities/sceptre/lifestealbeam.ron b/assets/common/abilities/sceptre/lifestealbeam.ron index 7fcdc5932f..3f233fac95 100644 --- a/assets/common/abilities/sceptre/lifestealbeam.ron +++ b/assets/common/abilities/sceptre/lifestealbeam.ron @@ -10,4 +10,5 @@ BasicBeam( energy_regen: 25, energy_drain: 0, orientation_behavior: Normal, + specifier: LifestealBeam ) \ No newline at end of file diff --git a/assets/common/abilities/staff/flamethrower.ron b/assets/common/abilities/staff/flamethrower.ron index 968285b222..060c23f821 100644 --- a/assets/common/abilities/staff/flamethrower.ron +++ b/assets/common/abilities/staff/flamethrower.ron @@ -10,4 +10,5 @@ BasicBeam( energy_regen: 0, energy_drain: 350, orientation_behavior: Normal, + specifier: Flamethrower, ) \ No newline at end of file diff --git a/assets/common/abilities/staffsimple/flamethrower.ron b/assets/common/abilities/staffsimple/flamethrower.ron index e87a65dca1..bbf0ce6783 100644 --- a/assets/common/abilities/staffsimple/flamethrower.ron +++ b/assets/common/abilities/staffsimple/flamethrower.ron @@ -10,4 +10,5 @@ BasicBeam( energy_regen: 0, energy_drain: 350, orientation_behavior: Normal, + specifier: Flamethrower, ) diff --git a/assets/common/abilities/unique/quadlowbeam/healingbeam.ron b/assets/common/abilities/unique/quadlowbeam/healingbeam.ron index 089c906bc8..ffbd29ff05 100644 --- a/assets/common/abilities/unique/quadlowbeam/healingbeam.ron +++ b/assets/common/abilities/unique/quadlowbeam/healingbeam.ron @@ -12,4 +12,5 @@ BasicBeam( //energy_cost: 50, energy_drain: 0, orientation_behavior: Normal, + specifier: HealingBeam, ) \ No newline at end of file diff --git a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron index 2b8313cfea..8d81986e27 100644 --- a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron +++ b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron @@ -10,4 +10,5 @@ BasicBeam( energy_regen: 0, energy_drain: 0, orientation_behavior: Normal, + specifier: Flamethrower, ) \ No newline at end of file diff --git a/assets/common/abilities/unique/turret/flamethrower.ron b/assets/common/abilities/unique/turret/flamethrower.ron index b06e2cf39e..6b865f8bfa 100644 --- a/assets/common/abilities/unique/turret/flamethrower.ron +++ b/assets/common/abilities/unique/turret/flamethrower.ron @@ -10,4 +10,5 @@ BasicBeam( energy_regen: 0, energy_drain: 0, orientation_behavior: Turret, + specifier: Flamethrower, ) \ No newline at end of file diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 11bc889e28..c6159ace1a 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -60,6 +60,7 @@ const int FIRE_BOWL = 18; const int SNOW = 19; const int EXPLOSION = 20; const int ICE = 21; +const int LIFESTEAL_BEAM = 22; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -340,6 +341,14 @@ void main() { vec4(vec3(0.4, 1.6 + 0.3 * sin(tick.x * 10 - lifetime * 3 + 4), 1.0 + 0.15 * sin(tick.x * 5 - lifetime * 5)), 1 /*0.3*/), spin_in_axis(inst_dir, tick.z) ); + } else if (inst_mode == LIFESTEAL_BEAM) { + f_reflect = 0.01; + attr = Attr( + spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time), + vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))), + vec4(vec3(1.0 + 0.3 * sin(tick.x + lifetime * 5), 1.25 + 0.2 * sin(tick.x * 10 - lifetime * 3 + 4), 0.7), 1 /*0.3*/), + spin_in_axis(inst_dir, tick.z) + ); } else if (inst_mode == ENERGY_NATURE) { f_reflect = 0.0; attr = Attr( diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 6b2bf66ee7..5c2441dc75 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -2,8 +2,8 @@ use crate::{ assets::{self, Asset}, combat, comp::{ - aura, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, Body, - CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate, + aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, + Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate, }, states::{ behavior::JoinData, @@ -224,6 +224,7 @@ pub enum CharacterAbility { energy_regen: f32, energy_drain: f32, orientation_behavior: basic_beam::MovementBehavior, + specifier: beam::FrontendSpecifier, }, CastAura { buildup_duration: f32, @@ -243,6 +244,7 @@ pub enum CharacterAbility { range: f32, max_angle: f32, energy_cost: f32, + specifier: beam::FrontendSpecifier, }, } @@ -1472,6 +1474,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { energy_regen, energy_drain, orientation_behavior, + specifier, } => CharacterState::BasicBeam(basic_beam::Data { static_data: basic_beam::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -1486,6 +1489,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { energy_drain: *energy_drain, ability_info, orientation_behavior: *orientation_behavior, + specifier: *specifier, }, timer: Duration::default(), stage_section: StageSection::Buildup, @@ -1520,6 +1524,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { range, max_angle, energy_cost, + specifier, } => CharacterState::HealingBeam(healing_beam::Data { static_data: healing_beam::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -1531,6 +1536,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { max_angle: *max_angle, energy_cost: *energy_cost, ability_info, + specifier: *specifier, }, timer: Duration::default(), stage_section: StageSection::Buildup, diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 60b722aed6..2ad006672f 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -11,6 +11,7 @@ pub struct Properties { pub speed: f32, pub duration: Duration, pub owner: Option, + pub specifier: FrontendSpecifier, } // TODO: Separate components out for cheaper network syncing @@ -44,3 +45,10 @@ pub struct Beam { impl Component for Beam { type Storage = IdvStorage; } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum FrontendSpecifier { + Flamethrower, + LifestealBeam, + HealingBeam, +} diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 6954c28a43..670936c7f9 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -1,5 +1,5 @@ use crate::{comp, uid::Uid}; -use comp::item::Reagent; +use comp::{beam, item::Reagent}; use serde::{Deserialize, Serialize}; use vek::*; @@ -24,7 +24,7 @@ pub enum Outcome { }, Beam { pos: Vec3, - heal: bool, + specifier: beam::FrontendSpecifier, }, ExpChange { uid: Uid, diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index c39182ef59..045bf1a86b 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -43,6 +43,8 @@ pub struct StaticData { pub orientation_behavior: MovementBehavior, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Used to specify the beam to the frontend + pub specifier: beam::FrontendSpecifier, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -141,6 +143,7 @@ impl CharacterBehavior for Data { speed, duration: self.static_data.beam_duration, owner: Some(*data.uid), + specifier: self.static_data.specifier, }; // Gets offsets let body_offsets = match data.body { diff --git a/common/src/states/healing_beam.rs b/common/src/states/healing_beam.rs index 94b2163fff..e5a6806ac5 100644 --- a/common/src/states/healing_beam.rs +++ b/common/src/states/healing_beam.rs @@ -33,6 +33,8 @@ pub struct StaticData { pub energy_cost: f32, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Used to specify the beam to the frontend + pub specifier: beam::FrontendSpecifier, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -106,6 +108,7 @@ impl CharacterBehavior for Data { speed, duration: self.static_data.beam_duration, owner: Some(*data.uid), + specifier: self.static_data.specifier, }; // Gets offsets let body_offsets = match data.body { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 2fb80f0bd9..a10b60c9d9 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -1,7 +1,6 @@ use crate::{sys, Server, StateExt}; use common::{ character::CharacterId, - combat, comp::{ self, aura::{Aura, AuraKind, AuraTarget}, @@ -175,10 +174,7 @@ pub fn handle_beam(server: &mut Server, properties: beam::Properties, pos: Pos, let ecs = state.ecs(); ecs.write_resource::>().push(Outcome::Beam { pos: pos.0, - heal: properties - .attack - .effects() - .any(|e| matches!(e.effect(), combat::CombatEffect::Heal(h) if *h > 0.0)), + specifier: properties.specifier, }); state.create_beam(properties, pos, ori).build(); } diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 47183dd40b..e90a3c6cb3 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -91,6 +91,7 @@ use client::Client; use common::{ assets::{self, AssetExt, AssetHandle}, comp::{ + beam, item::{ItemKind, Reagent, ToolKind}, object, Body, CharacterAbilityType, InventoryUpdateEvent, }, @@ -353,14 +354,15 @@ impl SfxMgr { let file_ref = "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up"; audio.play_sfx(file_ref, *pos, None); }, - Outcome::Beam { pos, heal } => { - if *heal { + Outcome::Beam { pos, specifier } => match specifier { + beam::FrontendSpecifier::LifestealBeam | beam::FrontendSpecifier::HealingBeam => { let file_ref = "voxygen.audio.sfx.abilities.staff_channeling"; audio.play_sfx(file_ref, *pos, None); - } else { + }, + beam::FrontendSpecifier::Flamethrower => { let file_ref = "voxygen.audio.sfx.abilities.flame_thrower"; audio.play_sfx(file_ref, *pos, None); - } + }, }, Outcome::ExpChange { .. } => {}, } diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 6100ae60df..69c9900584 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -118,6 +118,7 @@ pub enum ParticleMode { Snow = 19, Explosion = 20, Ice = 21, + LifestealBeam = 22, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 7161bbf88b..99d73c25a2 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -8,8 +8,7 @@ use crate::{ }; use common::{ assets::{AssetExt, DotVoxAsset}, - combat::CombatEffect, - comp::{item::Reagent, object, BeamSegment, Body, CharacterState, Ori, Pos, Shockwave}, + comp::{beam, item::Reagent, object, BeamSegment, Body, CharacterState, Ori, Pos, Shockwave}, figure::Segment, outcome::Outcome, resources::DeltaTime, @@ -401,104 +400,70 @@ impl ParticleMgr { .join() .filter(|(_, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time)) { - // + // TODO: Handle this less hackily. Done this way as beam segments are created + // every server tick, which is approximately 33 ms. Heartbeat scheduler used to + // account for clients with less than 30 fps because they start the creation + // time when the segments are received and could receive 2 at once + let beam_tick_count = 33.max(self.scheduler.heartbeats(Duration::from_millis(1))); let range = beam.properties.speed * beam.properties.duration.as_secs_f32(); - if beam - .properties - .attack - .effects() - .any(|e| matches!(e.effect(), CombatEffect::Heal(h) if *h > 0.0)) - { - // Emit a light when using healing - lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0)); - for i in 0..self.scheduler.heartbeats(Duration::from_millis(1)) { - self.particles.push(Particle::new_directed( - beam.properties.duration, - time + i as f64 / 1000.0, - ParticleMode::HealingBeam, + match beam.properties.specifier { + beam::FrontendSpecifier::Flamethrower => { + let mut rng = thread_rng(); + let (from, to) = (Vec3::::unit_z(), *ori.look_dir()); + let m = Mat3::::rotation_from_to_3d(from, to); + // Emit a light when using flames + lights.push(Light::new( pos.0, - pos.0 + *ori.look_dir() * range, + Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)), + 2.0, )); - /* - if let CharacterState::BasicBeam(b) = character_state { - if b.stage_section == StageSection::Cast { - if b.static_data.base_hps > 0.0 {// - // Emit a light when using healing - lights.push(Light::new(pos.0 + b.offset, Rgb::new(0.1, 1.0, 0.15), 1.0)); - for i in 0..self.scheduler.heartbeats(Duration::from_millis(1)) { - self.particles.push(Particle::new_directed( - b.static_data.beam_duration, - time + i as f64 / 1000.0, - ParticleMode::HealingBeam, - pos.0 + particle_ori * 0.5 + b.offset, - pos.0 + particle_ori * b.static_data.range + b.offset, - )); - } - } else { - let mut rng = thread_rng(); - let (from, to) = (Vec3::::unit_z(), particle_ori); - let m = Mat3::::rotation_from_to_3d(from, to); - // Emit a light when using flames - lights.push(Light::new( - pos.0 + b.offset, - Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)), - 2.0, - )); - self.particles.resize_with( - self.particles.len() - + 2 * usize::from( - self.scheduler.heartbeats(Duration::from_millis(1)), - ), - || { - let phi: f32 = - rng.gen_range(0.0..b.static_data.max_angle.to_radians()); - let theta: f32 = rng.gen_range(0.0..2.0 * PI); - let offset_z = Vec3::new( - phi.sin() * theta.cos(), - phi.sin() * theta.sin(), - phi.cos(), - ); - let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0); - Particle::new_directed( - b.static_data.beam_duration, - time, - ParticleMode::FlameThrower, - pos.0 + random_ori * 0.5 + b.offset, - pos.0 + random_ori * b.static_data.range + b.offset, - ) - }, - ); - } - */ - } - } else { - let mut rng = thread_rng(); - let (from, to) = (Vec3::::unit_z(), *ori.look_dir()); - let m = Mat3::::rotation_from_to_3d(from, to); - // Emit a light when using flames - lights.push(Light::new( - pos.0, - Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)), - 2.0, - )); - self.particles.resize_with( - self.particles.len() - + 2 * usize::from(self.scheduler.heartbeats(Duration::from_millis(1))), - || { - let phi: f32 = rng.gen_range(0.0..beam.properties.angle); - let theta: f32 = rng.gen_range(0.0..2.0 * PI); - let offset_z = - Vec3::new(phi.sin() * theta.cos(), phi.sin() * theta.sin(), phi.cos()); - let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0); - Particle::new_directed( + self.particles.resize_with( + self.particles.len() + 2 * usize::from(beam_tick_count), + || { + let phi: f32 = rng.gen_range(0.0..beam.properties.angle); + let theta: f32 = rng.gen_range(0.0..2.0 * PI); + let offset_z = Vec3::new( + phi.sin() * theta.cos(), + phi.sin() * theta.sin(), + phi.cos(), + ); + let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0); + Particle::new_directed( + beam.properties.duration, + time, + ParticleMode::FlameThrower, + pos.0, + pos.0 + random_ori * range, + ) + }, + ); + }, + beam::FrontendSpecifier::HealingBeam => { + // Emit a light when using healing + lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0)); + for i in 0..beam_tick_count { + self.particles.push(Particle::new_directed( beam.properties.duration, - time, - ParticleMode::FlameThrower, - pos.0, /* + random_ori */ - pos.0 + random_ori * range, - ) - }, - ); + time + i as f64 / 1000.0, + ParticleMode::HealingBeam, + pos.0, + pos.0 + *ori.look_dir() * range, + )); + } + }, + beam::FrontendSpecifier::LifestealBeam => { + // Emit a light when using lifesteal beam + lights.push(Light::new(pos.0, Rgb::new(0.8, 1.0, 0.5), 1.0)); + for i in 0..beam_tick_count { + self.particles.push(Particle::new_directed( + beam.properties.duration, + time + i as f64 / 1000.0, + ParticleMode::LifestealBeam, + pos.0, + pos.0 + *ori.look_dir() * range, + )); + } + }, } } }