diff --git a/assets/common/abilities/axe/cleave.ron b/assets/common/abilities/axe/cleave.ron index b4732b4a2f..d5d4f12893 100644 --- a/assets/common/abilities/axe/cleave.ron +++ b/assets/common/abilities/axe/cleave.ron @@ -1,22 +1,25 @@ -ComboMelee2( - strikes: [ - ( - melee_constructor: ( - kind: Slash( - damage: 4, - poise: 5, - knockback: 0, - energy_regen: 5, - ), - range: 3.0, - angle: 45.0, - ), - buildup_duration: 0.15, - swing_duration: 0.05, - hit_timing: 0.5, - recover_duration: 0.1, - ori_modifier: 0.6, +ChargedMelee( + energy_cost: 0, + energy_drain: 0, + melee_constructor: ( + kind: Slash( + damage: 0, + poise: 0, + knockback: 0, + energy_regen: 0, ), - ], - energy_cost_per_strike: 0, + scaled: Some(Slash( + damage: 20, + poise: 5, + knockback: 0, + energy_regen: 15, + )), + range: 4.5, + angle: 10.0, + ), + charge_duration: 0.7, + swing_duration: 0.1, + hit_timing: 0.2, + recover_duration: 0.2, + additional_combo: 4, ) \ No newline at end of file diff --git a/assets/common/abilities/axe/triple_chop.ron b/assets/common/abilities/axe/triple_chop.ron index df12779d61..3d07c92885 100644 --- a/assets/common/abilities/axe/triple_chop.ron +++ b/assets/common/abilities/axe/triple_chop.ron @@ -9,7 +9,7 @@ ComboMelee2( energy_regen: 5, ), range: 3.0, - angle: 45.0, + angle: 30.0, ), buildup_duration: 0.15, swing_duration: 0.05, @@ -26,7 +26,7 @@ ComboMelee2( energy_regen: 5, ), range: 3.0, - angle: 45.0, + angle: 30.0, ), buildup_duration: 0.15, swing_duration: 0.05, @@ -43,7 +43,7 @@ ComboMelee2( energy_regen: 5, ), range: 3.0, - angle: 45.0, + angle: 10.0, ), buildup_duration: 0.2, swing_duration: 0.05, diff --git a/common/src/combat.rs b/common/src/combat.rs index cee2ca5950..d9ead06a66 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -114,13 +114,16 @@ impl Attack { } #[must_use] - pub fn with_combo_increment(self) -> Self { + pub fn with_combo(self, combo: i32) -> Self { self.with_effect( - AttackEffect::new(None, CombatEffect::Combo(1)) + AttackEffect::new(None, CombatEffect::Combo(combo)) .with_requirement(CombatRequirement::AnyDamage), ) } + #[must_use] + pub fn with_combo_increment(self) -> Self { self.with_combo(1) } + pub fn effects(&self) -> impl Iterator { self.effects.iter() } pub fn compute_damage_reduction( diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index ba75793181..f4100f4253 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -709,6 +709,8 @@ pub enum CharacterAbility { specifier: Option, damage_effect: Option, #[serde(default)] + additional_combo: i32, + #[serde(default)] meta: AbilityMeta, }, ChargedRanged { @@ -898,6 +900,7 @@ impl Default for CharacterAbility { multi_target: None, damage_effect: None, simultaneous_hits: 1, + combo_gain: 1, }, ori_modifier: 1.0, meta: Default::default(), @@ -1276,6 +1279,7 @@ impl CharacterAbility { specifier: _, ref mut damage_effect, meta: _, + additional_combo: _, } => { *swing_duration /= stats.speed; *buildup_strike = buildup_strike @@ -2398,6 +2402,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { melee_constructor, specifier, damage_effect, + additional_combo, meta: _, } => CharacterState::ChargedMelee(charged_melee::Data { static_data: charged_melee::StaticData { @@ -2413,6 +2418,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { ability_info, specifier: *specifier, damage_effect: *damage_effect, + additional_combo: *additional_combo, }, stage_section: if buildup_strike.is_some() { StageSection::Buildup diff --git a/common/src/comp/melee.rs b/common/src/comp/melee.rs index aa53c7f487..bcc53deeed 100644 --- a/common/src/comp/melee.rs +++ b/common/src/comp/melee.rs @@ -51,6 +51,7 @@ impl Component for Melee { } fn default_simultaneous_hits() -> u32 { 1 } +fn default_combo_gain() -> i32 { 1 } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -66,6 +67,8 @@ pub struct MeleeConstructor { pub damage_effect: Option, #[serde(default = "default_simultaneous_hits")] pub simultaneous_hits: u32, + #[serde(default = "default_combo_gain")] + pub combo_gain: i32, } impl MeleeConstructor { @@ -128,7 +131,7 @@ impl MeleeConstructor { .with_effect(energy) .with_effect(poise) .with_effect(knockback) - .with_combo_increment() + .with_combo(self.combo_gain) }, Stab { damage, @@ -179,7 +182,7 @@ impl MeleeConstructor { .with_effect(energy) .with_effect(poise) .with_effect(knockback) - .with_combo_increment() + .with_combo(self.combo_gain) }, Bash { damage, @@ -222,7 +225,7 @@ impl MeleeConstructor { .with_effect(energy) .with_effect(poise) .with_effect(knockback) - .with_combo_increment() + .with_combo(self.combo_gain) }, NecroticVortex { damage, @@ -260,7 +263,7 @@ impl MeleeConstructor { .with_damage(damage) .with_crit(crit_chance, crit_mult) .with_effect(knockback) - .with_combo_increment() + .with_combo(self.combo_gain) }, SonicWave { damage, @@ -299,7 +302,7 @@ impl MeleeConstructor { .with_crit(crit_chance, crit_mult) .with_effect(poise) .with_effect(knockback) - .with_combo_increment() + .with_combo(self.combo_gain) }, }; @@ -437,6 +440,12 @@ impl MeleeConstructor { self.damage_effect = self.damage_effect.map(|de| de.adjusted_by_stats(stats)); self } + + #[must_use] + pub fn with_combo(mut self, combo: i32) -> Self { + self.combo_gain = combo; + self + } } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index 7f11212b0d..3fcd53ae53 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -37,6 +37,8 @@ pub struct StaticData { pub specifier: Option, /// Adds an effect onto the main damage of the attack pub damage_effect: Option, + /// The actual additional combo is modified by duration of charge + pub additional_combo: i32, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -153,11 +155,18 @@ impl CharacterBehavior for Data { let crit_data = get_crit_data(data, self.static_data.ability_info); let tool_stats = get_tool_stats(data, self.static_data.ability_info); + let additional_combo = if self.static_data.additional_combo != 0 { + let increment = 1.0 / self.static_data.additional_combo as f32; + (self.charge_amount / increment + 0.5).floor() as i32 + } else { + 0 + }; data.updater.insert( data.entity, self.static_data .melee_constructor + .with_combo(1 + additional_combo) .handle_scaling(self.charge_amount) .create_melee(crit_data, tool_stats), ); diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 7388915fbc..7073f7a572 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -277,6 +277,7 @@ fn create_test_melee(static_data: StaticData) -> Melee { multi_target: None, damage_effect: None, simultaneous_hits: 1, + combo_gain: 0, }; melee.create_melee((0.0, 0.0), tool::Stats::one()) } diff --git a/common/src/states/idle.rs b/common/src/states/idle.rs index c89263f2af..05d1ab9b3d 100644 --- a/common/src/states/idle.rs +++ b/common/src/states/idle.rs @@ -51,9 +51,13 @@ impl CharacterBehavior for Data { update } - fn swap_equipped_weapons(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { + fn swap_equipped_weapons( + &self, + data: &JoinData, + output_events: &mut OutputEvents, + ) -> StateUpdate { let mut update = StateUpdate::from(data); - attempt_swap_equipped_weapons(data, &mut update); + attempt_swap_equipped_weapons(data, &mut update, output_events); update } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 0f5304fbc6..24dbf0ebc5 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -900,7 +900,11 @@ pub fn handle_wallrun(data: &JoinData<'_>, update: &mut StateUpdate) -> bool { } } /// Checks that player can Swap Weapons and updates `Loadout` if so -pub fn attempt_swap_equipped_weapons(data: &JoinData<'_>, update: &mut StateUpdate) { +pub fn attempt_swap_equipped_weapons( + data: &JoinData<'_>, + update: &mut StateUpdate, + output_events: &mut OutputEvents, +) { if data .inventory .and_then(|inv| inv.equipped(EquipSlot::InactiveMainhand)) @@ -910,6 +914,11 @@ pub fn attempt_swap_equipped_weapons(data: &JoinData<'_>, update: &mut StateUpda .and_then(|inv| inv.equipped(EquipSlot::InactiveOffhand)) .is_some() { + // Reset combo to 0 after manipulating loadout + output_events.emit_server(ServerEvent::ComboChange { + entity: data.entity, + change: -data.combo.map_or(0, |c| c.counter() as i32), + }); update.swap_equipped_weapons = true; } } @@ -995,6 +1004,11 @@ pub fn handle_manipulate_loadout( update: &mut StateUpdate, inv_action: InventoryAction, ) { + // Reset combo to 0 after manipulating loadout + output_events.emit_server(ServerEvent::ComboChange { + entity: data.entity, + change: -data.combo.map_or(0, |c| c.counter() as i32), + }); match inv_action { InventoryAction::Use(slot @ Slot::Inventory(inv_slot)) => { // If inventory action is using a slot, and slot is in the inventory diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs index c8ad0b16f5..7ba7f82fb2 100644 --- a/common/src/states/wielding.rs +++ b/common/src/states/wielding.rs @@ -36,9 +36,13 @@ impl CharacterBehavior for Data { update } - fn swap_equipped_weapons(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { + fn swap_equipped_weapons( + &self, + data: &JoinData, + output_events: &mut OutputEvents, + ) -> StateUpdate { let mut update = StateUpdate::from(data); - attempt_swap_equipped_weapons(data, &mut update); + attempt_swap_equipped_weapons(data, &mut update, output_events); update } diff --git a/voxygen/anim/src/character/chargeswing.rs b/voxygen/anim/src/character/chargeswing.rs index d83ecc0c08..88ce552c10 100644 --- a/voxygen/anim/src/character/chargeswing.rs +++ b/voxygen/anim/src/character/chargeswing.rs @@ -234,6 +234,45 @@ impl Animation for ChargeswingAnimation { next.control.orientation.rotate_z(move2 * -1.8); next.control.position += Vec3::new(move2 * 14.0, 0.0, 0.0); }, + Some("common.abilities.axe.cleave") => { + let (move1, move2, move3, tension) = match stage_section { + Some(StageSection::Charge) => { + next.main_weapon_trail = false; + next.off_weapon_trail = false; + (anim_time.min(1.0), 0.0, 0.0, (anim_time * 20.0).sin()) + }, + Some(StageSection::Action) => (1.0, anim_time.powi(2), 0.0, 0.0), + Some(StageSection::Recover) => { + next.main_weapon_trail = false; + next.off_weapon_trail = false; + (1.0, 1.0, anim_time, 0.0) + }, + _ => (0.0, 0.0, 0.0, 0.0), + }; + let pullback = 1.0 - move3; + let move1 = move1 * pullback; + let move2 = move2 * pullback; + + next.hand_l.position = Vec3::new(s_a.ahl.0, s_a.ahl.1, s_a.ahl.2); + next.hand_l.orientation = + Quaternion::rotation_x(s_a.ahl.3) * Quaternion::rotation_y(s_a.ahl.4); + next.hand_r.position = Vec3::new(s_a.ahr.0, s_a.ahr.1, s_a.ahr.2); + next.hand_r.orientation = + Quaternion::rotation_x(s_a.ahr.3) * Quaternion::rotation_z(s_a.ahr.5); + + next.control.position = Vec3::new( + s_a.ac.0 + move1 * 7.0, + s_a.ac.1 + move1 * -4.0, + s_a.ac.2 + move1 * 18.0 + tension / 5.0, + ); + next.control.orientation = + Quaternion::rotation_x(s_a.ac.3 + move1 * -1.0 + tension / 30.0) + * Quaternion::rotation_y(s_a.ac.4) + * Quaternion::rotation_z(s_a.ac.5); + + next.control.orientation.rotate_x(move2 * -3.0); + next.control.position += Vec3::new(0.0, move2 * 8.0, move2 * -30.0); + }, _ => { let lab: f32 = 1.0;