diff --git a/assets/common/abilities/custom/minotaur/cleave.ron b/assets/common/abilities/custom/minotaur/cleave.ron index 19d9c02392..b6b0f88a18 100644 --- a/assets/common/abilities/custom/minotaur/cleave.ron +++ b/assets/common/abilities/custom/minotaur/cleave.ron @@ -14,4 +14,5 @@ ChargedMelee( swing_duration: 0.1, hit_timing: 0.8, recover_duration: 0.5, + specifier: Some(GroundCleave), ) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index dd3ca7b466..d94534313d 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -64,6 +64,8 @@ const int LIFESTEAL_BEAM = 22; const int CULTIST_FLAME = 23; const int STATIC_SMOKE = 24; const int BLOOD = 25; +const int ENRAGED = 26; +const int BIG_SHRAPNEL = 27; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -218,6 +220,17 @@ void main() { vec4(vec3(0.25), 1), spin_in_axis(vec3(1,0,0),0) ); + } else if (inst_mode == BIG_SHRAPNEL) { + float brown_color = 0.05 + 0.1 * rand1; + attr = Attr( + linear_motion( + vec3(0), + normalize(vec3(rand4, rand5, rand6)) * 15.0 + grav_vel(earth_gravity) + ), + vec3(5 * (1 - percent())), + vec4(vec3(brown_color, brown_color / 2, 0), 1), + spin_in_axis(vec3(1,0,0),0) + ); } else if (inst_mode == FIREWORK_BLUE) { f_reflect = 0.0; // Fire doesn't reflect light, it emits it attr = Attr( @@ -421,6 +434,15 @@ void main() { vec4(1, 0, 0, 1), spin_in_axis(vec3(1,0,0),0) ); + } else if (inst_mode == ENRAGED) { + f_reflect = 0.0; + float red_color = 1.2 + 0.3 * rand3; + attr = Attr( + (inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1, + vec3((3.5 * (1 - slow_start(0.2)))), + vec4(red_color, 0.0, 0.0, 1), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); } else { attr = Attr( linear_motion( diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 9e0525b3e0..37a4da3fe8 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -191,6 +191,7 @@ pub enum CharacterAbility { swing_duration: f32, hit_timing: f32, recover_duration: f32, + specifier: Option, }, ChargedRanged { energy_cost: f32, @@ -1452,6 +1453,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { recover_duration, range, max_angle, + specifier, } => CharacterState::ChargedMelee(charged_melee::Data { static_data: charged_melee::StaticData { energy_cost: *energy_cost, @@ -1470,6 +1472,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { hit_timing: *hit_timing, recover_duration: Duration::from_secs_f32(*recover_duration), ability_info, + specifier: *specifier, }, stage_section: StageSection::Charge, timer: Duration::default(), diff --git a/common/src/outcome.rs b/common/src/outcome.rs index afdef6ae47..f4044aa3a4 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -68,6 +68,9 @@ pub enum Outcome { pos: Vec3, state: PoiseState, }, + Bonk { + pos: Vec3, + }, } impl Outcome { @@ -81,7 +84,8 @@ impl Outcome { | Outcome::SummonedCreature { pos, .. } | Outcome::Damage { pos, .. } | Outcome::Block { pos, .. } - | Outcome::PoiseChange { pos, .. } => Some(*pos), + | Outcome::PoiseChange { pos, .. } + | Outcome::Bonk { pos } => Some(*pos), Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)), Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None, } diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index 30285b964d..6dab18b9d4 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -1,6 +1,8 @@ use crate::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement}, comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate}, + event::LocalEvent, + outcome::Outcome, states::{ behavior::{CharacterBehavior, JoinData}, utils::{StageSection, *}, @@ -45,6 +47,8 @@ pub struct StaticData { pub recover_duration: Duration, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Used to specify the melee attack to the frontend + pub specifier: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -199,6 +203,15 @@ impl CharacterBehavior for Data { }) .filter(|(_, tool)| tool == &Some(ToolKind::Pick)), }); + + // Send local event used for frontend shenanigans + update + .local_events + .push_front(LocalEvent::CreateOutcome(Outcome::Bonk { + pos: data.pos.0 + + *data.ori.look_dir() + * (data.body.radius() + self.static_data.range), + })); } else if self.timer < self.static_data.swing_duration { // Swings update.character = CharacterState::ChargedMelee(Data { @@ -250,3 +263,8 @@ impl CharacterBehavior for Data { update } } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum FrontendSpecifier { + GroundCleave, +} diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 60bf4b4594..40f74c9886 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -325,6 +325,10 @@ impl SfxMgr { false, ); }, + Outcome::Bonk { pos, .. } => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Explosion); + audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), false); + }, Outcome::ProjectileShot { pos, body, .. } => { match body { Body::Object( diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 32a73abfde..32b9cebd37 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -122,6 +122,8 @@ pub enum ParticleMode { CultistFlame = 23, StaticSmoke = 24, Blood = 25, + Enraged = 26, + BigShrapnel = 27, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 53ae2aea42..813cc4ef4b 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -215,6 +215,16 @@ impl ParticleMgr { }); } }, + Outcome::Bonk { pos, .. } => { + self.particles.resize_with(self.particles.len() + 100, || { + Particle::new( + Duration::from_millis(1000), + time, + ParticleMode::BigShrapnel, + *pos, + ) + }); + }, Outcome::ProjectileShot { .. } | Outcome::Beam { .. } | Outcome::ExpChange { .. } @@ -643,6 +653,36 @@ impl ParticleMgr { }, ); }, + CharacterState::SelfBuff(c) => { + use buff::BuffKind; + if let BuffKind::Frenzied = c.static_data.buff_kind { + if matches!(c.stage_section, StageSection::Cast) { + self.particles.resize_with( + self.particles.len() + + usize::from( + self.scheduler.heartbeats(Duration::from_millis(5)), + ), + || { + let start_pos = pos.0 + + Vec3::new( + body.radius(), + body.radius(), + body.height() / 2.0, + ) + .map(|d| d * rng.gen_range(-1.0..1.0)); + let end_pos = pos.0 + (start_pos - pos.0) * 6.0; + Particle::new_directed( + Duration::from_secs(1), + time, + ParticleMode::Enraged, + start_pos, + end_pos, + ) + }, + ); + } + } + }, _ => {}, } } @@ -818,9 +858,9 @@ impl ParticleMgr { .join() { for (buff_kind, _) in buffs.kinds.iter() { - #[allow(clippy::single_match)] + use buff::BuffKind; match buff_kind { - buff::BuffKind::Cursed | buff::BuffKind::Burning => { + BuffKind::Cursed | BuffKind::Burning => { self.particles.resize_with( self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(15))), @@ -850,6 +890,29 @@ impl ParticleMgr { }, ); }, + BuffKind::Frenzied => { + self.particles.resize_with( + self.particles.len() + + usize::from(self.scheduler.heartbeats(Duration::from_millis(15))), + || { + let start_pos = pos.0 + + Vec3::new(body.radius(), body.radius(), body.height() / 2.0) + .map(|d| d * rng.gen_range(-1.0..1.0)); + let end_pos = start_pos + + Vec3::unit_z() * body.height() + + Vec3::::zero() + .map(|_| rng.gen_range(-1.0..1.0)) + .normalized(); + Particle::new_directed( + Duration::from_secs(1), + time, + ParticleMode::Enraged, + start_pos, + end_pos, + ) + }, + ); + }, _ => {}, } }