diff --git a/assets/common/abilities/axe/spin.ron b/assets/common/abilities/axe/spin.ron index 47807ae3e7..1a552cc7f8 100644 --- a/assets/common/abilities/axe/spin.ron +++ b/assets/common/abilities/axe/spin.ron @@ -4,12 +4,14 @@ SpinMelee( recover_duration: 0.2, base_damage: 70, base_poise_damage: 55, - knockback: 0.0, + knockback: ( strength: 0.0, direction: Away), range: 3.5, + damage_effect: None, energy_cost: 100, is_infinite: true, movement_behavior: AxeHover, is_interruptible: false, forward_speed: 0.0, num_spins: 1, + specifier: None, ) diff --git a/assets/common/abilities/sword/spin.ron b/assets/common/abilities/sword/spin.ron index b8ff65f93e..0de44ce51c 100644 --- a/assets/common/abilities/sword/spin.ron +++ b/assets/common/abilities/sword/spin.ron @@ -4,12 +4,14 @@ SpinMelee( recover_duration: 0.5, base_damage: 160, base_poise_damage: 25, - knockback: 10.0, + knockback: ( strength: 10.0, direction: Away), range: 3.5, + damage_effect: None, energy_cost: 150, is_infinite: false, movement_behavior: ForwardGround, is_interruptible: true, forward_speed: 1.0, num_spins: 3, + specifier: None, ) diff --git a/assets/common/abilities/unique/mindflayer/necroticvortex.ron b/assets/common/abilities/unique/mindflayer/necroticvortex.ron index ca0309ede7..7082e7a62f 100644 --- a/assets/common/abilities/unique/mindflayer/necroticvortex.ron +++ b/assets/common/abilities/unique/mindflayer/necroticvortex.ron @@ -1 +1,17 @@ -BasicBlock \ No newline at end of file +SpinMelee( + buildup_duration: 0.6, + swing_duration: 0.2, + recover_duration: 0.6, + base_damage: 70.0, + base_poise_damage: 0.0, + knockback: ( strength: 8.0, direction: Towards), + range: 15.0, + damage_effect: Some(Lifesteal(1.0)), + energy_cost: 0.0, + is_infinite: true, + movement_behavior: Stationary, + is_interruptible: false, + forward_speed: 0.0, + num_spins: 1, + specifier: Some(CultistVortex), +) diff --git a/assets/common/abilities/unique/stonegolemfist/spin.ron b/assets/common/abilities/unique/stonegolemfist/spin.ron index 33d821f51f..1aaf1445e2 100644 --- a/assets/common/abilities/unique/stonegolemfist/spin.ron +++ b/assets/common/abilities/unique/stonegolemfist/spin.ron @@ -4,12 +4,14 @@ SpinMelee( recover_duration: 0.1, base_damage: 500, base_poise_damage: 30, - knockback: 0.0, + knockback: ( strength: 0.0, direction: Away), range: 7.5, + damage_effect: None, energy_cost: 0, is_infinite: false, movement_behavior: GolemHover, is_interruptible: false, forward_speed: 0.0, num_spins: 1, + specifier: None, ) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 0d747368f4..1e632c8ff0 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -395,10 +395,10 @@ void main() { ); } else if (inst_mode == CULTIST_FLAME) { f_reflect = 0.0; // Fire doesn't reflect light, it emits it - float purp_color = 0.8 + 0.5 * rand3; + float purp_color = 0.9 + 0.3 * rand3; attr = Attr( (inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1, - vec3((2.5 * (1 - slow_start(0.2)))), + vec3((3.5 * (1 - slow_start(0.2)))), vec4(purp_color, 0.0, purp_color, 1), spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) ); diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index b0da9b5097..07a80cc7ae 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -156,14 +156,16 @@ pub enum CharacterAbility { recover_duration: f32, base_damage: f32, base_poise_damage: f32, - knockback: f32, + knockback: Knockback, range: f32, + damage_effect: Option, energy_cost: f32, is_infinite: bool, movement_behavior: spin_melee::MovementBehavior, is_interruptible: bool, forward_speed: f32, num_spins: u32, + specifier: Option, }, ChargedMelee { energy_cost: f32, @@ -1301,12 +1303,14 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { base_poise_damage, knockback, range, + damage_effect, energy_cost, is_infinite, movement_behavior, is_interruptible, forward_speed, num_spins, + specifier, } => CharacterState::SpinMelee(spin_melee::Data { static_data: spin_melee::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -1316,6 +1320,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { base_poise_damage: *base_poise_damage, knockback: *knockback, range: *range, + damage_effect: *damage_effect, energy_cost: *energy_cost, is_infinite: *is_infinite, movement_behavior: *movement_behavior, @@ -1323,6 +1328,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { forward_speed: *forward_speed, num_spins: *num_spins, ability_info, + specifier: *specifier, }, timer: Duration::default(), spins_remaining: *num_spins - 1, diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 4dce57dff4..134c2a45ea 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -1,12 +1,14 @@ use crate::{ - combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement}, + combat::{ + Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement, Damage, + DamageSource, GroupTarget, Knockback, + }, comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate}, consts::GRAVITY, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, }, - Damage, DamageSource, GroupTarget, Knockback, KnockbackDir, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -26,9 +28,11 @@ pub struct StaticData { /// Base poise damage pub base_poise_damage: f32, /// Knockback - pub knockback: f32, + pub knockback: Knockback, /// Range pub range: f32, + /// Adds an effect onto the main damage of the attack + pub damage_effect: Option, /// Energy cost per attack pub energy_cost: f32, /// Whether spin state is infinite @@ -43,6 +47,8 @@ pub struct StaticData { pub num_spins: u32, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Used to specify the beam to the frontend + pub specifier: Option, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -65,7 +71,7 @@ impl CharacterBehavior for Data { let mut update = StateUpdate::from(data); match self.static_data.movement_behavior { - MovementBehavior::ForwardGround => {}, + MovementBehavior::ForwardGround | MovementBehavior::Stationary => {}, MovementBehavior::AxeHover => { let new_vel_z = update.vel.0.z + GRAVITY * data.dt.0 * 0.5; update.vel.0 = Vec3::new(0.0, 0.0, new_vel_z) + data.inputs.move_dir * 5.0; @@ -110,21 +116,23 @@ impl CharacterBehavior for Data { .with_requirement(CombatRequirement::AnyDamage); let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), - CombatEffect::Knockback(Knockback { - strength: self.static_data.knockback, - direction: KnockbackDir::Away, - }), + CombatEffect::Knockback(self.static_data.knockback), ) .with_requirement(CombatRequirement::AnyDamage); - let buff = CombatEffect::Buff(CombatBuff::default_physical()); - let damage = AttackDamage::new( + let mut damage = AttackDamage::new( Damage { source: DamageSource::Melee, value: self.static_data.base_damage as f32, }, Some(GroupTarget::OutOfGroup), - ) - .with_effect(buff); + ); + match self.static_data.damage_effect { + Some(effect) => damage = damage.with_effect(effect), + None => { + let buff = CombatEffect::Buff(CombatBuff::default_physical()); + damage = damage.with_effect(buff); + }, + } let (crit_chance, crit_mult) = get_crit_data(data, self.static_data.ability_info); let attack = Attack::default() @@ -204,6 +212,8 @@ impl CharacterBehavior for Data { stage_section: StageSection::Recover, ..*self }); + // Remove melee attack component + data.updater.remove::(data.entity); } }, StageSection::Recover => { @@ -242,7 +252,13 @@ impl CharacterBehavior for Data { #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum MovementBehavior { + Stationary, ForwardGround, AxeHover, GolemHover, } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum FrontendSpecifier { + CultistVortex, +} diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 20c5d6a2dd..2c75426f05 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -16,6 +16,7 @@ use common::{ outcome::Outcome, resources::DeltaTime, spiral::Spiral2d, + states, terrain::TerrainChunk, vol::{RectRasterableVol, SizedVol}, }; @@ -195,6 +196,7 @@ impl ParticleMgr { self.maintain_shockwave_particles(scene_data); self.maintain_aura_particles(scene_data); self.maintain_buff_particles(scene_data); + self.maintain_spin_melee_particles(scene_data); } else { // remove all particle lifespans self.particles.clear(); @@ -563,6 +565,7 @@ impl ParticleMgr { let state = scene_data.state; let ecs = state.ecs(); let time = state.get_time(); + let mut rng = thread_rng(); for (pos, auras) in ( &ecs.read_storage::(), @@ -577,7 +580,6 @@ impl ParticleMgr { kind: buff::BuffKind::ProtectingWard, .. } => { - let mut rng = thread_rng(); let heartbeats = self.scheduler.heartbeats(Duration::from_millis(5)); self.particles.resize_with( self.particles.len() @@ -792,14 +794,13 @@ impl ParticleMgr { let ecs = state.ecs(); let time = state.get_time(); - for (_i, (_entity, pos, ori, shockwave)) in ( + for (_entity, pos, ori, shockwave) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), ) .join() - .enumerate() { let elapsed = time - shockwave.creation.unwrap_or_default(); @@ -862,6 +863,56 @@ impl ParticleMgr { } } + fn maintain_spin_melee_particles(&mut self, scene_data: &SceneData) { + let state = scene_data.state; + let ecs = state.ecs(); + let time = state.get_time(); + let mut rng = thread_rng(); + + for (pos, character_state) in ( + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() + { + if let CharacterState::SpinMelee(c) = character_state { + if let Some(specifier) = c.static_data.specifier { + match specifier { + states::spin_melee::FrontendSpecifier::CultistVortex => { + let heartbeats = self.scheduler.heartbeats(Duration::from_millis(3)); + self.particles.resize_with( + self.particles.len() + + c.static_data.range.powi(2) as usize + * usize::from(heartbeats) + / 150, + || { + let rand_dist = + c.static_data.range * (1.0 - rng.gen::().powi(10)); + let init_pos = Vec3::new( + 2.0 * rng.gen::() - 1.0, + 2.0 * rng.gen::() - 1.0, + 0.0, + ) + .normalized() + * rand_dist + + pos.0 + + Vec3::unit_z() * 0.05; + Particle::new_directed( + Duration::from_millis(900), + time, + ParticleMode::CultistFlame, + init_pos, + pos.0, + ) + }, + ); + }, + } + } + } + } + } + fn upload_particles(&mut self, renderer: &mut Renderer) { span!(_guard, "upload_particles", "ParticleMgr::upload_particles"); let all_cpu_instances = self