diff --git a/assets/common/items/npc_weapons/sword/zweihander_sword_0.ron b/assets/common/items/npc_weapons/sword/zweihander_sword_0.ron index b75c8c515c..7c9a956393 100644 --- a/assets/common/items/npc_weapons/sword/zweihander_sword_0.ron +++ b/assets/common/items/npc_weapons/sword/zweihander_sword_0.ron @@ -6,7 +6,7 @@ ItemDef( kind: Sword("Zweihander0"), stats: ( equip_time_millis: 500, - power: 0.2, + power: 0.75, ), ) ), diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index a44789e880..9df90f404f 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -94,19 +94,19 @@ ], threshold: 0.5, ), - Attack(ComboMelee(First), Sword): ( + Attack(ComboMelee(1), Sword): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], threshold: 0.7, ), - Attack(ComboMelee(Second), Sword): ( + Attack(ComboMelee(2), Sword): ( files: [ "voxygen.audio.sfx.abilities.separated_second_swing", ], threshold: 0.7, ), - Attack(ComboMelee(Third), Sword): ( + Attack(ComboMelee(3), Sword): ( files: [ "voxygen.audio.sfx.abilities.separated_third_swing", ], @@ -174,19 +174,7 @@ ], threshold: 0.5, ), - Attack(ComboMelee(First), Axe): ( - files: [ - "voxygen.audio.sfx.abilities.swing", - ], - threshold: 0.7, - ), - Attack(ComboMelee(Second), Axe): ( - files: [ - "voxygen.audio.sfx.abilities.swing", - ], - threshold: 0.7, - ), - Attack(ComboMelee(Third), Axe): ( + Attack(BasicMelee, Axe): ( files: [ "voxygen.audio.sfx.abilities.swing", ], diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index f56c8671a0..5139e25c51 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -20,7 +20,7 @@ pub enum CharacterAbilityType { ChargedRanged, DashMelee, BasicBlock, - ComboMelee, + ComboMelee(u32), LeapMelee, SpinMelee, GroundShockwave, @@ -35,7 +35,7 @@ impl From<&CharacterState> for CharacterAbilityType { CharacterState::DashMelee(_) => Self::DashMelee, CharacterState::BasicBlock => Self::BasicBlock, CharacterState::LeapMelee(_) => Self::LeapMelee, - CharacterState::ComboMelee(_) => Self::ComboMelee, + CharacterState::ComboMelee(data) => Self::ComboMelee(data.stage), CharacterState::SpinMelee(_) => Self::SpinMelee, CharacterState::ChargedRanged(_) => Self::ChargedRanged, CharacterState::GroundShockwave(_) => Self::ChargedRanged, @@ -85,6 +85,7 @@ pub enum CharacterAbility { swing_duration: Duration, recover_duration: Duration, infinite_charge: bool, + is_interruptible: bool, }, BasicBlock, Roll, @@ -95,6 +96,7 @@ pub enum CharacterAbility { energy_increase: u32, speed_increase: f32, max_speed_increase: f32, + is_interruptible: bool, }, LeapMelee { energy_cost: u32, @@ -334,6 +336,7 @@ impl From<&CharacterAbility> for CharacterState { swing_duration, recover_duration, infinite_charge, + is_interruptible, } => CharacterState::DashMelee(dash_melee::Data { static_data: dash_melee::StaticData { base_damage: *base_damage, @@ -349,6 +352,7 @@ impl From<&CharacterAbility> for CharacterState { charge_duration: *charge_duration, swing_duration: *swing_duration, recover_duration: *recover_duration, + is_interruptible: *is_interruptible, }, end_charge: false, timer: Duration::default(), @@ -367,6 +371,7 @@ impl From<&CharacterAbility> for CharacterState { energy_increase, speed_increase, max_speed_increase, + is_interruptible, } => CharacterState::ComboMelee(combo_melee::Data { stage: 1, num_stages: stage_data.len() as u32, @@ -380,6 +385,7 @@ impl From<&CharacterAbility> for CharacterState { next_stage: false, speed_increase: 1.0 - *speed_increase, max_speed_increase: *max_speed_increase - 1.0, + is_interruptible: *is_interruptible, }), CharacterAbility::LeapMelee { energy_cost: _, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 84054fe2d2..d8b9bb4bb3 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -124,7 +124,7 @@ impl Tool { base_damage: (100.0 * self.base_power()) as u32, max_damage: (120.0 * self.base_power()) as u32, damage_increase: (10.0 * self.base_power()) as u32, - knockback: 5.0, + knockback: 8.0, range: 4.0, angle: 30.0, base_buildup_duration: Duration::from_millis(500), @@ -150,7 +150,7 @@ impl Tool { base_damage: (130.0 * self.base_power()) as u32, max_damage: (170.0 * self.base_power()) as u32, damage_increase: (20.0 * self.base_power()) as u32, - knockback: 7.5, + knockback: 14.0, range: 6.0, angle: 10.0, base_buildup_duration: Duration::from_millis(500), @@ -164,11 +164,12 @@ impl Tool { energy_increase: 20, speed_increase: 0.05, max_speed_increase: 1.8, + is_interruptible: true, }, DashMelee { energy_cost: 200, - base_damage: (60.0 * self.base_power()) as u32, - max_damage: (180.0 * self.base_power()) as u32, + base_damage: (120.0 * self.base_power()) as u32, + max_damage: (260.0 * self.base_power()) as u32, base_knockback: 5.0, max_knockback: 10.0, range: 5.0, @@ -178,8 +179,9 @@ impl Tool { buildup_duration: Duration::from_millis(200), charge_duration: Duration::from_millis(400), swing_duration: Duration::from_millis(100), - recover_duration: Duration::from_millis(750), + recover_duration: Duration::from_millis(500), infinite_charge: true, + is_interruptible: true, }, ], Axe(_) => vec![ diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index e88bfc3121..b8d5f78d47 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -43,7 +43,7 @@ pub struct Data { pub num_stages: u32, /// Number of consecutive strikes pub combo: u32, - /// Data for first stage + /// Data for each stage pub stage_data: Vec, /// Initial energy gain per strike pub initial_energy_gain: u32, @@ -57,10 +57,13 @@ pub struct Data { pub stage_section: StageSection, /// Whether the state should go onto the next stage pub next_stage: bool, - /// (100% - speed_increase) is percentage speed increases from current to max when combo increases + /// (100% - speed_increase) is percentage speed increases from current to + /// max when combo increases pub speed_increase: f32, /// (100% + max_speed_increase) is the max attack speed pub max_speed_increase: f32, + /// Whether the state can be interrupted by other abilities + pub is_interruptible: bool, } impl CharacterBehavior for Data { @@ -68,10 +71,22 @@ impl CharacterBehavior for Data { let mut update = StateUpdate::from(data); handle_orientation(data, &mut update, 1.0); - handle_move(data, &mut update, 0.1); + handle_move(data, &mut update, 0.3); let stage_index = (self.stage - 1) as usize; + // Allows for other states to interrupt this state + if self.is_interruptible && !data.inputs.primary.is_pressed() { + if data.inputs.roll.is_pressed() { + handle_dodge_input(data, &mut update); + return update; + } + if data.inputs.secondary.is_pressed() { + handle_ability2_input(data, &mut update); + return update; + } + } + if self.stage_section == StageSection::Buildup && self.timer < self.stage_data[stage_index].base_buildup_duration { @@ -86,12 +101,17 @@ impl CharacterBehavior for Data { energy_increase: self.energy_increase, timer: self .timer - .checked_add(Duration::from_secs_f32((1.0 + self.max_speed_increase * (1.0 - self.speed_increase.powi(self.combo as i32))) * data.dt.0)) + .checked_add(Duration::from_secs_f32( + (1.0 + self.max_speed_increase + * (1.0 - self.speed_increase.powi(self.combo as i32))) + * data.dt.0, + )) .unwrap_or_default(), stage_section: self.stage_section, next_stage: self.next_stage, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); } else if self.stage_section == StageSection::Buildup { // Transitions to swing section of stage @@ -108,6 +128,7 @@ impl CharacterBehavior for Data { next_stage: self.next_stage, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); // Hit attempt @@ -130,7 +151,7 @@ impl CharacterBehavior for Data { forward_move( data, &mut update, - 0.1, + 0.3, self.stage_data[stage_index].forward_movement, ); @@ -145,12 +166,17 @@ impl CharacterBehavior for Data { energy_increase: self.energy_increase, timer: self .timer - .checked_add(Duration::from_secs_f32((1.0 + self.max_speed_increase * (1.0 - self.speed_increase.powi(self.combo as i32))) * data.dt.0)) + .checked_add(Duration::from_secs_f32( + (1.0 + self.max_speed_increase + * (1.0 - self.speed_increase.powi(self.combo as i32))) + * data.dt.0, + )) .unwrap_or_default(), stage_section: self.stage_section, next_stage: self.next_stage, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); } else if self.stage_section == StageSection::Swing { // Transitions to recover section of stage @@ -167,6 +193,7 @@ impl CharacterBehavior for Data { next_stage: self.next_stage, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); } else if self.stage_section == StageSection::Recover && self.timer < self.stage_data[stage_index].base_recover_duration @@ -184,12 +211,17 @@ impl CharacterBehavior for Data { energy_increase: self.energy_increase, timer: self .timer - .checked_add(Duration::from_secs_f32((1.0 + self.max_speed_increase * (1.0 - self.speed_increase.powi(self.combo as i32))) * data.dt.0)) + .checked_add(Duration::from_secs_f32( + (1.0 + self.max_speed_increase + * (1.0 - self.speed_increase.powi(self.combo as i32))) + * data.dt.0, + )) .unwrap_or_default(), stage_section: self.stage_section, next_stage: true, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); } else { update.character = CharacterState::ComboMelee(Data { @@ -202,12 +234,17 @@ impl CharacterBehavior for Data { energy_increase: self.energy_increase, timer: self .timer - .checked_add(Duration::from_secs_f32((1.0 + self.max_speed_increase * (1.0 - self.speed_increase.powi(self.combo as i32))) * data.dt.0)) + .checked_add(Duration::from_secs_f32( + (1.0 + self.max_speed_increase + * (1.0 - self.speed_increase.powi(self.combo as i32))) + * data.dt.0, + )) .unwrap_or_default(), stage_section: self.stage_section, next_stage: self.next_stage, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); } } else if self.next_stage { @@ -225,6 +262,7 @@ impl CharacterBehavior for Data { next_stage: false, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); } else { // Done @@ -253,6 +291,7 @@ impl CharacterBehavior for Data { next_stage: self.next_stage, speed_increase: self.speed_increase, max_speed_increase: self.max_speed_increase, + is_interruptible: self.is_interruptible, }); data.updater.remove::(data.entity); update.energy.change_by(energy, EnergySource::HitEnemy); diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 2eb462652c..ac1ad8fd5a 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -5,6 +5,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use std::time::Duration; +use vek::Vec3; /// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -35,11 +36,14 @@ pub struct StaticData { pub swing_duration: Duration, /// How long the state has until exiting pub recover_duration: Duration, + /// Whether the state can be interrupted by other abilities + pub is_interruptible: bool, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { - /// Struct containing data that does not change over the course of the character state + /// Struct containing data that does not change over the course of the + /// character state pub static_data: StaticData, /// Whether the charge should end pub end_charge: bool, @@ -58,7 +62,21 @@ impl CharacterBehavior for Data { handle_orientation(data, &mut update, 1.0); handle_move(data, &mut update, 0.1); - if self.stage_section == StageSection::Buildup && self.timer < self.static_data.buildup_duration { + // Allows for other states to interrupt this state + if self.static_data.is_interruptible && !data.inputs.secondary.is_pressed() { + if data.inputs.roll.is_pressed() { + handle_dodge_input(data, &mut update); + return update; + } + if data.inputs.primary.is_pressed() { + handle_ability1_input(data, &mut update); + return update; + } + } + + if self.stage_section == StageSection::Buildup + && self.timer < self.static_data.buildup_duration + { // Build up update.character = CharacterState::DashMelee(Data { static_data: self.static_data, @@ -80,23 +98,26 @@ impl CharacterBehavior for Data { exhausted: self.exhausted, }) } else if self.stage_section == StageSection::Charge - && ((self.timer < self.static_data.charge_duration && !self.static_data.infinite_charge) - || (data.inputs.secondary.is_pressed() && self.static_data.infinite_charge)) + && (self.timer < self.static_data.charge_duration + || (self.static_data.infinite_charge && data.inputs.secondary.is_pressed())) && update.energy.current() > 0 && !self.end_charge { // Forward movement forward_move(data, &mut update, 0.1, self.static_data.forward_speed); - // Hit attempt - if !self.exhausted { + // Hit attempt (also checks if player is moving) + if !self.exhausted && update.vel.0.distance_squared(Vec3::zero()) > 1.0 { let charge_frac = (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()) .min(1.0); - let damage = (self.static_data.max_damage as f32 - self.static_data.base_damage as f32) * charge_frac + let damage = (self.static_data.max_damage as f32 + - self.static_data.base_damage as f32) + * charge_frac + self.static_data.base_damage as f32; - let knockback = - (self.static_data.max_knockback - self.static_data.base_knockback) * charge_frac + self.static_data.base_knockback; + let knockback = (self.static_data.max_knockback - self.static_data.base_knockback) + * charge_frac + + self.static_data.base_knockback; data.updater.insert(data.entity, Attacking { base_healthchange: -damage as i32, range: self.static_data.range, @@ -107,7 +128,8 @@ impl CharacterBehavior for Data { }); } - // This logic basically just decides if a charge should end, and prevents the character state spamming attacks while checking if it has hit something + // This logic basically just decides if a charge should end, and prevents the + // character state spamming attacks while checking if it has hit something if !self.exhausted { update.character = CharacterState::DashMelee(Data { static_data: self.static_data, @@ -119,42 +141,29 @@ impl CharacterBehavior for Data { stage_section: StageSection::Charge, exhausted: true, }) - } else { - if let Some(attack) = data.attacking { - if attack.applied && attack.hit_count > 0 { - update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: !self.static_data.infinite_charge, - timer: self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: false, - }) - } else if attack.applied { - update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, - timer: self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: false, - }) - } else { - update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: !self.static_data.infinite_charge, - timer: self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: self.exhausted, - }) - } + } else if let Some(attack) = data.attacking { + if attack.applied && attack.hit_count > 0 { + update.character = CharacterState::DashMelee(Data { + static_data: self.static_data, + end_charge: !self.static_data.infinite_charge, + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + stage_section: StageSection::Charge, + exhausted: false, + }) + } else if attack.applied { + update.character = CharacterState::DashMelee(Data { + static_data: self.static_data, + end_charge: self.end_charge, + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + stage_section: StageSection::Charge, + exhausted: false, + }) } else { update.character = CharacterState::DashMelee(Data { static_data: self.static_data, @@ -167,6 +176,17 @@ impl CharacterBehavior for Data { exhausted: self.exhausted, }) } + } else { + update.character = CharacterState::DashMelee(Data { + static_data: self.static_data, + end_charge: !self.static_data.infinite_charge, + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + stage_section: StageSection::Charge, + exhausted: self.exhausted, + }) } // Consumes energy if there's enough left and charge has not stopped @@ -183,7 +203,9 @@ impl CharacterBehavior for Data { stage_section: StageSection::Swing, exhausted: self.exhausted, }) - } else if self.stage_section == StageSection::Swing && self.timer < self.static_data.swing_duration { + } else if self.stage_section == StageSection::Swing + && self.timer < self.static_data.swing_duration + { // Swings update.character = CharacterState::DashMelee(Data { static_data: self.static_data, @@ -204,7 +226,8 @@ impl CharacterBehavior for Data { stage_section: StageSection::Recover, exhausted: self.exhausted, }) - } else if self.stage_section == StageSection::Recover && self.timer < self.static_data.recover_duration + } else if self.stage_section == StageSection::Recover + && self.timer < self.static_data.recover_duration { // Recover update.character = CharacterState::DashMelee(Data { diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index c671b051f9..7e10e4c86b 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -98,10 +98,9 @@ pub fn forward_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, BASE_HUMANOID_AIR_ACCEL }; - update.vel.0 = update.vel.0 - + Vec2::broadcast(data.dt.0) - * accel - * (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * forward); + update.vel.0 += Vec2::broadcast(data.dt.0) + * accel + * (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * forward); handle_orientation(data, update, data.body.base_ori_rate() * efficiency); } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 4329b48550..99134bb36f 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -115,11 +115,17 @@ impl<'a> System<'a> for Sys { let mut body = entity.body; let name = entity.name.unwrap_or_else(|| "Unnamed".to_string()); let alignment = entity.alignment; - let main_tool = entity.main_tool; + let mut main_tool = entity.main_tool; let mut stats = comp::Stats::new(name, body); // let damage = stats.level.level() as i32; TODO: Make NPC base damage // non-linearly depend on their level + if entity.is_giant { + main_tool = Some(comp::Item::new_from_asset_expect( + "common.items.npc_weapons.sword.zweihander_sword_0", + )); + } + let mut loadout = LoadoutBuilder::build_loadout(body, alignment, main_tool).build(); let mut scale = entity.scale; @@ -152,24 +158,7 @@ impl<'a> System<'a> for Sys { ); } loadout = comp::Loadout { - active_item: Some(comp::ItemConfig { - item: comp::Item::new_from_asset_expect( - "common.items.npc_weapons.sword.zweihander_sword_0", - ), - ability1: Some(CharacterAbility::BasicMelee { - energy_cost: 0, - buildup_duration: Duration::from_millis(800), - recover_duration: Duration::from_millis(200), - base_healthchange: -100, - knockback: 0.0, - range: 3.5, - max_angle: 60.0, - }), - ability2: None, - ability3: None, - block_ability: None, - dodge_ability: None, - }), + active_item, second_item: None, shoulder: Some(comp::Item::new_from_asset_expect( "common.items.armor.shoulder.plate_0", diff --git a/voxygen/src/anim/src/character/dash.rs b/voxygen/src/anim/src/character/dash.rs index 1af9ec84c7..60643bfcac 100644 --- a/voxygen/src/anim/src/character/dash.rs +++ b/voxygen/src/anim/src/character/dash.rs @@ -13,7 +13,12 @@ pub struct Input { pub struct DashAnimation; impl Animation for DashAnimation { - type Dependency = (Option, Option, f64, Option); + type Dependency = ( + Option, + Option, + f64, + Option, + ); type Skeleton = CharacterSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -47,21 +52,13 @@ impl Animation for DashAnimation { Some(ToolKind::Sword(_)) => { if let Some(stage_section) = stage_section { match stage_section { - StageSection::Buildup => { - - }, - StageSection::Charge => { - - }, - StageSection::Swing => { - - }, - StageSection::Recover => { - - } + StageSection::Buildup => {}, + StageSection::Charge => {}, + StageSection::Swing => {}, + StageSection::Recover => {}, } } - + next.head.position = Vec3::new( 0.0, -2.0 + skeleton_attr.head.0, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index fa34a685ed..df5f49062f 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -901,34 +901,26 @@ impl FigureMgr { let stage_time = s.timer.as_secs_f64(); let stage_progress = match s.stage_section { StageSection::Buildup => { - stage_time - / s.static_data - .buildup_duration - .as_secs_f64() + stage_time / s.static_data.buildup_duration.as_secs_f64() }, StageSection::Charge => { - stage_time - / s.static_data - .charge_duration - .as_secs_f64() + stage_time / s.static_data.charge_duration.as_secs_f64() }, StageSection::Swing => { - stage_time - / s.static_data - .swing_duration - .as_secs_f64() + stage_time / s.static_data.swing_duration.as_secs_f64() }, StageSection::Recover => { - stage_time - / s.static_data - .recover_duration - .as_secs_f64() + stage_time / s.static_data.recover_duration.as_secs_f64() }, - _ => 0.0, }; anim::character::DashAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, time, Some(s.stage_section)), + ( + active_tool_kind, + second_tool_kind, + time, + Some(s.stage_section), + ), stage_progress, &mut state_animation_rate, skeleton_attr,