diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 9e564e71a1..90cc7097ba 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -52,6 +52,7 @@ const int GROUND_SHOCKWAVE = 12; const int HEALING_BEAM = 13; const int ENERGY_NATURE = 14; const int FLAMETHROWER = 15; +const int FIRE_SHOCKWAVE = 16; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -294,6 +295,13 @@ void main() { vec4(1, 0.6 + rand5 * 0.3 - 0.6 * lifetime / inst_lifespan, 0, 0.8 - 0.6 * lifetime / inst_lifespan), spin_in_axis(vec3(rand6, rand7, rand8), lifetime / inst_lifespan * 10 + 3 * rand9) ); + } else if (inst_mode == FIRE_SHOCKWAVE) { + attr = Attr( + vec3(rand0, rand1, lifetime * 10 + rand2), + vec3(1.6 + rand3 * 1.5 + 10 * (lifetime + inst_lifespan)), + vec4(1, 0.6 + rand7 * 0.3 - 5 * inst_lifespan + 2 * lifetime, 0, 0.8 - 3.5 * inst_lifespan), + spin_in_axis(vec3(rand3, rand4, rand5), rand6) + ); } else { attr = Attr( linear_motion( diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index b24dbe6201..5d7181b104 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -3,7 +3,10 @@ use crate::{ item::{armor::Protection, Item, ItemKind}, Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate, }, - states::{utils::StageSection, *}, + states::{ + utils::{AbilityKey, StageSection}, + *, + }, sys::character_behavior::JoinData, }; use arraygen::Arraygen; @@ -187,7 +190,6 @@ pub enum CharacterAbility { requires_ground: bool, }, BasicBeam { - energy_cost: u32, buildup_duration: Duration, recover_duration: Duration, beam_duration: Duration, @@ -198,7 +200,9 @@ pub enum CharacterAbility { max_angle: f32, lifesteal_eff: f32, energy_regen: u32, + energy_cost: u32, energy_drain: u32, + ability_key: AbilityKey, }, } @@ -252,9 +256,9 @@ impl CharacterAbility { .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), - CharacterAbility::BasicBeam { energy_cost, .. } => update + CharacterAbility::BasicBeam { energy_drain, .. } => update .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) + .try_change_by(-(*energy_drain as i32), EnergySource::Ability) .is_ok(), _ => true, } @@ -640,7 +644,6 @@ impl From<&CharacterAbility> for CharacterState { requires_ground: *requires_ground, }), CharacterAbility::BasicBeam { - energy_cost: _, buildup_duration, recover_duration, beam_duration, @@ -651,7 +654,9 @@ impl From<&CharacterAbility> for CharacterState { max_angle, lifesteal_eff, energy_regen, + energy_cost, energy_drain, + ability_key, } => CharacterState::BasicBeam(basic_beam::Data { static_data: basic_beam::StaticData { buildup_duration: *buildup_duration, @@ -664,7 +669,9 @@ impl From<&CharacterAbility> for CharacterState { max_angle: *max_angle, lifesteal_eff: *lifesteal_eff, energy_regen: *energy_regen, + energy_cost: *energy_cost, energy_drain: *energy_drain, + ability_key: *ability_key, }, timer: Duration::default(), stage_section: StageSection::Buildup, diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 387bc98461..1a0d311ea4 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -12,7 +12,7 @@ pub struct Properties { pub heal: u32, pub lifesteal_eff: f32, pub energy_regen: u32, - pub energy_drain: u32, + pub energy_cost: u32, pub duration: Duration, pub owner: Option, } diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 93f713d7d9..3450737137 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -6,7 +6,7 @@ use crate::{ body::object, projectile, Body, CharacterAbility, Explosion, Gravity, LightEmitter, Projectile, }, - states::combo_melee, + states::{combo_melee, utils::AbilityKey}, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -365,7 +365,6 @@ impl Tool { }], Sceptre(_) => vec![ BasicBeam { - energy_cost: 0, buildup_duration: Duration::from_millis(250), recover_duration: Duration::from_millis(250), beam_duration: Duration::from_secs(1), @@ -376,7 +375,9 @@ impl Tool { max_angle: 1.0, lifesteal_eff: 0.20, energy_regen: 50, - energy_drain: 100, + energy_cost: 100, + energy_drain: 0, + ability_key: AbilityKey::Mouse1, }, BasicRanged { energy_cost: 800, @@ -461,7 +462,6 @@ impl Tool { projectile_speed: 60.0, }, BasicBeam { - energy_cost: 0, buildup_duration: Duration::from_millis(250), recover_duration: Duration::from_millis(250), beam_duration: Duration::from_millis(500), @@ -471,8 +471,10 @@ impl Tool { range: 15.0, max_angle: 22.5, lifesteal_eff: 0.0, - energy_regen: 50, + energy_regen: 0, + energy_cost: 0, energy_drain: 0, + ability_key: AbilityKey::Mouse2, }, Shockwave { energy_cost: 0, diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index baa3b253cf..8d6e9fb9e8 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -1,7 +1,7 @@ use crate::{ - comp::{beam, humanoid, Body, CharacterState, Ori, Pos, StateUpdate}, + comp::{beam, humanoid, Body, CharacterState, EnergySource, Ori, Pos, StateUpdate}, event::ServerEvent, - states::utils::{StageSection, *}, + states::utils::*, sync::Uid, sys::character_behavior::{CharacterBehavior, JoinData}, }; @@ -34,7 +34,11 @@ pub struct StaticData { /// Energy regened per second for damage ticks pub energy_regen: u32, /// Energy consumed per second for heal ticks + pub energy_cost: u32, + /// Energy drained per pub energy_drain: u32, + /// What key is used to press ability + pub ability_key: AbilityKey, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -101,15 +105,17 @@ impl CharacterBehavior for Data { } }, StageSection::Cast => { - if data.inputs.primary.is_pressed() { + if ability_key_is_pressed(data, self.static_data.ability_key) + && (self.static_data.energy_drain == 0 || update.energy.current() > 0) + { let damage = (self.static_data.base_dps as f32 / self.static_data.tick_rate) as u32; let heal = (self.static_data.base_hps as f32 / self.static_data.tick_rate) as u32; let energy_regen = (self.static_data.energy_regen as f32 / self.static_data.tick_rate) as u32; - let energy_drain = - (self.static_data.energy_drain as f32 / self.static_data.tick_rate) as u32; + let energy_cost = + (self.static_data.energy_cost as f32 / self.static_data.tick_rate) as u32; let speed = self.static_data.range / self.static_data.beam_duration.as_secs_f32(); let properties = beam::Properties { @@ -119,7 +125,7 @@ impl CharacterBehavior for Data { heal, lifesteal_eff: self.static_data.lifesteal_eff, energy_regen, - energy_drain, + energy_cost, duration: self.static_data.beam_duration, owner: Some(*data.uid), }; @@ -137,6 +143,12 @@ impl CharacterBehavior for Data { particle_ori: Some(*data.inputs.look_dir), offset: self.offset, }); + + // Consumes energy if there's enough left and ability key is held down + update.energy.change_by( + -(self.static_data.energy_drain as f32 * data.dt.0) as i32, + EnergySource::Ability, + ); } else { update.character = CharacterState::BasicBeam(Data { static_data: self.static_data, diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index cc671158f4..fd59e00b3f 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -1,6 +1,6 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, - states::utils::{StageSection, *}, + states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index f336e7f491..2b09a10203 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -1,6 +1,6 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, - states::utils::{StageSection, *}, + states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 5d5cfb4445..8c97b99c0a 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -1,6 +1,6 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, - states::utils::{StageSection, *}, + states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 4578548d5d..f7327299ce 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -355,6 +355,14 @@ pub fn handle_interrupt(data: &JoinData, update: &mut StateUpdate) { handle_dodge_input(data, update); } +pub fn ability_key_is_pressed(data: &JoinData, ability_key: AbilityKey) -> bool { + match ability_key { + AbilityKey::Mouse1 => data.inputs.primary.is_pressed(), + AbilityKey::Mouse2 => data.inputs.secondary.is_pressed(), + AbilityKey::Skill1 => data.inputs.ability3.is_pressed(), + } +} + /// Determines what portion a state is in. Used in all attacks (eventually). Is /// used to control aspects of animation code, as well as logic within the /// character states. @@ -368,3 +376,10 @@ pub enum StageSection { Shoot, Movement, } + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum AbilityKey { + Mouse1, + Mouse2, + Skill1, +} diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index d39a4b4c3a..d39a8e299f 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -232,7 +232,7 @@ impl<'a> System<'a> for Sys { if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) { if energy_mut .try_change_by( - -(beam_segment.energy_drain as i32), // Stamina use + -(beam_segment.energy_cost as i32), // Stamina use EnergySource::Ability, ) .is_ok() diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 22ccb56491..bc71c54e38 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -112,6 +112,7 @@ pub enum ParticleMode { HealingBeam = 13, EnergyNature = 14, FlameThrower = 15, + FireShockwave = 16, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index ca4d755e6b..1489d59302 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -544,24 +544,23 @@ impl ParticleMgr { let theta = ori.0.y.atan2(ori.0.x); let dtheta = radians / distance; - if shockwave.properties.requires_ground { - // 1 / 3 the size of terrain voxel - let scale = 1.0 / 3.0; + let heartbeats = self.scheduler.heartbeats(Duration::from_millis(1)); - let scaled_speed = shockwave.properties.speed * scale; + for heartbeat in 0..heartbeats { + if shockwave.properties.requires_ground { + // 1 / 3 the size of terrain voxel + let scale = 1.0 / 3.0; - let heartbeats = self - .scheduler - .heartbeats(Duration::from_millis(scaled_speed as u64)); - let new_particle_count = distance / scale * heartbeats as f32; - self.particles.reserve(new_particle_count as usize); + let scaled_speed = shockwave.properties.speed * scale; - for heartbeat in 0..heartbeats { let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32; let distance = shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation); + let new_particle_count = distance / scale as f32; + self.particles.reserve(new_particle_count as usize); + for d in 0..((distance / scale) as i32) { let arc_position = theta - radians / 2.0 + dtheta * d as f32 * scale; @@ -577,8 +576,21 @@ impl ParticleMgr { position_snapped, )); } + } else { + for d in 0..10 * distance as i32 { + let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 10.0; + + let position = pos.0 + + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0); + + self.particles.push(Particle::new( + Duration::from_secs_f32(distance / 50.0), + time, + ParticleMode::FireShockwave, + position, + )); + } } - } else { } } }