diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dd760e5e5..5cdaa41841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The inventory can now be expanded to fill the whole window - Added /dropall admin command (drops all inventory items on the ground) - Skill trees +- Lactose tolerant golems ### Changed diff --git a/assets/common/abilities/axe/spin.ron b/assets/common/abilities/axe/spin.ron index e9660d27d4..a26b860d02 100644 --- a/assets/common/abilities/axe/spin.ron +++ b/assets/common/abilities/axe/spin.ron @@ -7,7 +7,7 @@ SpinMelee( range: 3.5, energy_cost: 100, is_infinite: true, - is_helicopter: true, + movement_behavior: AxeHover, is_interruptible: false, forward_speed: 0.0, num_spins: 1, diff --git a/assets/common/abilities/sword/spin.ron b/assets/common/abilities/sword/spin.ron index 237bd7fdcc..64edbfb839 100644 --- a/assets/common/abilities/sword/spin.ron +++ b/assets/common/abilities/sword/spin.ron @@ -7,7 +7,7 @@ SpinMelee( range: 3.5, energy_cost: 150, is_infinite: false, - is_helicopter: false, + movement_behavior: ForwardGround, is_interruptible: true, forward_speed: 1.0, num_spins: 3, diff --git a/assets/common/abilities/unique/stonegolemfist/shockwave.ron b/assets/common/abilities/unique/stonegolemfist/shockwave.ron index ac29382ee5..d161a02d62 100644 --- a/assets/common/abilities/unique/stonegolemfist/shockwave.ron +++ b/assets/common/abilities/unique/stonegolemfist/shockwave.ron @@ -6,9 +6,9 @@ Shockwave( damage: 500, knockback: TowardsUp(40.0), shockwave_angle: 90.0, - shockwave_vertical_angle: 15.0, - shockwave_speed: 20.0, - shockwave_duration: 2000, + shockwave_vertical_angle: 90.0, + shockwave_speed: 50.0, + shockwave_duration: 1000, requires_ground: true, move_efficiency: 0.05, ) \ No newline at end of file diff --git a/assets/common/abilities/unique/stonegolemfist/spin.ron b/assets/common/abilities/unique/stonegolemfist/spin.ron new file mode 100644 index 0000000000..06f2756ffb --- /dev/null +++ b/assets/common/abilities/unique/stonegolemfist/spin.ron @@ -0,0 +1,14 @@ +SpinMelee( + buildup_duration: 100, + swing_duration: 300, + recover_duration: 100, + base_damage: 500, + knockback: 0.0, + range: 7.5, + energy_cost: 0, + is_infinite: false, + movement_behavior: GolemHover, + is_interruptible: false, + forward_speed: 0.0, + num_spins: 1, +) \ No newline at end of file diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron index b478222f92..bf0d1dc533 100644 --- a/assets/common/abilities/weapon_ability_manifest.ron +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -54,7 +54,9 @@ Unique(StoneGolemFist): ( primary: "common.abilities.unique.stonegolemfist.basic", secondary: "common.abilities.unique.stonegolemfist.shockwave", - skills: [], + skills: [ + "common.abilities.unique.stonegolemfist.spin", + ], ), Unique(BeastClaws): ( primary: "common.abilities.unique.beastclaws.basic", diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index c78f5d4cfc..7fdbaf2c1e 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -152,7 +152,7 @@ pub enum CharacterAbility { range: f32, energy_cost: u32, is_infinite: bool, - is_helicopter: bool, + movement_behavior: spin_melee::MovementBehavior, is_interruptible: bool, forward_speed: f32, num_spins: u32, @@ -653,11 +653,15 @@ impl CharacterAbility { ref mut swing_duration, ref mut energy_cost, ref mut is_infinite, - ref mut is_helicopter, + ref mut movement_behavior, .. } => { *is_infinite = skillset.has_skill(Axe(SInfinite)); - *is_helicopter = skillset.has_skill(Axe(SHelicopter)); + *movement_behavior = if skillset.has_skill(Axe(SHelicopter)) { + spin_melee::MovementBehavior::AxeHover + } else { + spin_melee::MovementBehavior::ForwardGround + }; if let Ok(Some(level)) = skillset.skill_level(Axe(SDamage)) { *base_damage = (*base_damage as f32 * 1.3_f32.powi(level.into())) as u32; @@ -1251,7 +1255,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { range, energy_cost, is_infinite, - is_helicopter, + movement_behavior, is_interruptible, forward_speed, num_spins, @@ -1265,7 +1269,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { range: *range, energy_cost: *energy_cost, is_infinite: *is_infinite, - is_helicopter: *is_helicopter, + movement_behavior: *movement_behavior, is_interruptible: *is_interruptible, forward_speed: *forward_speed, num_spins: *num_spins, diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index f2c1ad16d8..9d301f3e6c 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -30,8 +30,8 @@ pub struct StaticData { pub energy_cost: u32, /// Whether spin state is infinite pub is_infinite: bool, - /// Used to maintain classic axe spin physics - pub is_helicopter: bool, + /// Used to dictate how movement functions in this state + pub movement_behavior: MovementBehavior, /// Whether the state can be interrupted by other abilities pub is_interruptible: bool, /// Used for forced forward movement @@ -61,9 +61,15 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); - if self.static_data.is_helicopter { - 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; + match self.static_data.movement_behavior { + MovementBehavior::ForwardGround => {}, + 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; + }, + MovementBehavior::GolemHover => { + update.vel.0 = Vec3::new(0.0, 0.0, 20.0) + *data.inputs.look_dir * 25.0; + }, } if !ability_key_is_pressed(data, self.static_data.ability_key) { @@ -116,7 +122,10 @@ impl CharacterBehavior for Data { knockback: Knockback::Away(self.static_data.knockback), }); } else if self.timer < self.static_data.swing_duration { - if !self.static_data.is_helicopter { + if matches!( + self.static_data.movement_behavior, + MovementBehavior::ForwardGround + ) { handle_forced_movement( data, &mut update, @@ -194,3 +203,10 @@ impl CharacterBehavior for Data { update } } + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum MovementBehavior { + ForwardGround, + AxeHover, + GolemHover, +} diff --git a/common/sys/src/agent.rs b/common/sys/src/agent.rs index a6e263d33b..c695a17dfb 100644 --- a/common/sys/src/agent.rs +++ b/common/sys/src/agent.rs @@ -896,6 +896,9 @@ impl<'a> System<'a> for Sys { } else if dist_sqrd < MAX_CHASE_DIST.powi(2) || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) { + if vel.0.is_approx_zero() { + inputs.ability3.set_state(true); + } if dist_sqrd < MAX_CHASE_DIST.powi(2) { *been_close = true; } diff --git a/voxygen/anim/src/golem/mod.rs b/voxygen/anim/src/golem/mod.rs index 5b9a8fb9b6..a3bcf69f60 100644 --- a/voxygen/anim/src/golem/mod.rs +++ b/voxygen/anim/src/golem/mod.rs @@ -3,11 +3,12 @@ pub mod idle; pub mod jump; pub mod run; pub mod shockwave; +pub mod spinmelee; // Reexports pub use self::{ alpha::AlphaAnimation, idle::IdleAnimation, jump::JumpAnimation, run::RunAnimation, - shockwave::ShockwaveAnimation, + shockwave::ShockwaveAnimation, spinmelee::SpinMeleeAnimation, }; use super::{make_bone, vek::*, FigureBoneData, Skeleton}; diff --git a/voxygen/anim/src/golem/spinmelee.rs b/voxygen/anim/src/golem/spinmelee.rs new file mode 100644 index 0000000000..7cda164165 --- /dev/null +++ b/voxygen/anim/src/golem/spinmelee.rs @@ -0,0 +1,87 @@ +use super::{ + super::{vek::*, Animation}, + GolemSkeleton, SkeletonAttr, +}; +use common::states::utils::StageSection; +use std::f32::consts::PI; + +pub struct SpinMeleeAnimation; + +impl Animation for SpinMeleeAnimation { + type Dependency = Option; + type Skeleton = GolemSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"golem_spinmelee\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "golem_spinmelee")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + stage_section: Self::Dependency, + anim_time: f64, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + let (movement1, movement2, movement3) = match stage_section { + Some(StageSection::Buildup) => ((anim_time as f32).powf(0.25), 0.0, 0.0), + Some(StageSection::Swing) => (1.0, anim_time as f32, 0.0), + Some(StageSection::Recover) => (1.0, 1.0, (anim_time as f32).powf(4.0)), + _ => (0.0, 0.0, 0.0), + }; + + let pullback = 1.0 - movement3; + + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1) * 1.02; + next.head.orientation = + Quaternion::rotation_z(movement2 * -2.0 * PI) * Quaternion::rotation_x(-0.2); + + next.upper_torso.position = Vec3::new(0.0, s_a.upper_torso.0, s_a.upper_torso.1) / 8.0; + next.upper_torso.orientation = Quaternion::rotation_z(movement2 * 2.0 * PI); + + next.lower_torso.position = + Vec3::new(0.0, s_a.lower_torso.0, s_a.lower_torso.1 + movement1 * 5.0); + next.lower_torso.orientation = Quaternion::rotation_z(movement2 * -2.0 * PI); + + next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); + next.shoulder_l.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_x(movement1 * 1.2 * pullback); + + next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2); + next.shoulder_r.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_x(movement1 * -1.2 * pullback); + + next.hand_l.position = Vec3::new(-s_a.hand.0, s_a.hand.1, s_a.hand.2); + next.hand_l.orientation = Quaternion::rotation_x(movement1 * -0.2 * pullback); + + next.hand_r.position = Vec3::new(s_a.hand.0, s_a.hand.1, s_a.hand.2); + next.hand_r.orientation = Quaternion::rotation_x(movement1 * 0.2 * pullback); + + next.leg_l.position = Vec3::new( + -s_a.leg.0 + movement1 * 3.0, + s_a.leg.1, + s_a.leg.2 + movement1 * 5.0, + ) * 1.02; + next.leg_l.orientation = Quaternion::rotation_x(0.0); + + next.leg_r.position = Vec3::new( + s_a.leg.0 - movement1 * 3.0, + s_a.leg.1, + s_a.leg.2 + movement1 * 5.0, + ) * 1.02; + next.leg_r.orientation = Quaternion::rotation_x(0.0); + + next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2 + movement1 * 5.0); + next.foot_l.orientation = Quaternion::rotation_x(0.0); + + next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2 + movement1 * 5.0); + next.foot_r.orientation = Quaternion::rotation_x(0.0); + + next.torso.position = Vec3::new(0.0, 0.0, 0.0); + next.torso.orientation = Quaternion::rotation_z(0.0); + + next + } +} diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index d12c8e1be2..792f28d2e2 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -3128,6 +3128,31 @@ impl FigureMgr { skeleton_attr, ) }, + CharacterState::SpinMelee(s) => { + let stage_progress = { + let stage_time = s.timer.as_secs_f64(); + match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f64() + }, + StageSection::Swing => { + stage_time / s.static_data.swing_duration.as_secs_f64() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f64() + }, + _ => 0.0, + } + }; + + anim::golem::SpinMeleeAnimation::update_skeleton( + &target_base, + Some(s.stage_section), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, // TODO! _ => target_base, };