diff --git a/assets/common/abilities/sceptre/healingbeam.ron b/assets/common/abilities/sceptre/healingbeam.ron index 3c5822dbb2..8b6b235b6f 100644 --- a/assets/common/abilities/sceptre/healingbeam.ron +++ b/assets/common/abilities/sceptre/healingbeam.ron @@ -11,4 +11,5 @@ BasicBeam( energy_regen: 25, energy_cost: 50, energy_drain: 0, + orientation_behavior: Normal, ) \ No newline at end of file diff --git a/assets/common/abilities/staff/flamethrower.ron b/assets/common/abilities/staff/flamethrower.ron index 42a2571d4a..4d886dbff5 100644 --- a/assets/common/abilities/staff/flamethrower.ron +++ b/assets/common/abilities/staff/flamethrower.ron @@ -11,4 +11,5 @@ BasicBeam( energy_regen: 0, energy_cost: 1, energy_drain: 350, + orientation_behavior: Normal, ) \ No newline at end of file diff --git a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron index f8df4454c5..3b8117c98b 100644 --- a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron +++ b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron @@ -11,4 +11,5 @@ BasicBeam( energy_regen: 0, energy_cost: 0, energy_drain: 0, + orientation_behavior: Normal, ) \ No newline at end of file diff --git a/assets/common/abilities/unique/turret/basic.ron b/assets/common/abilities/unique/turret/basic.ron index c1a324eb49..34880554e1 100644 --- a/assets/common/abilities/unique/turret/basic.ron +++ b/assets/common/abilities/unique/turret/basic.ron @@ -1,15 +1,15 @@ -BasicRanged( +BasicBeam( + buildup_duration: 250, + recover_duration: 250, + beam_duration: 500, + base_hps: 0, + base_dps: 150, + tick_rate: 3.0, + range: 30.0, + max_angle: 5.0, + lifesteal_eff: 0.0, + energy_regen: 0, energy_cost: 0, - buildup_duration: 500, - recover_duration: 300, - projectile: Arrow( - damage: 200.0, - knockback: 5.0, - energy_regen: 100, - ), - projectile_body: Object(ArrowTurret), - projectile_light: None, - projectile_gravity: Some(Gravity(0.1)), - projectile_speed: 100.0, - can_continue: true, + energy_drain: 0, + orientation_behavior: Turret(5.0), ) \ No newline at end of file diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 5e9d904b17..b171a85b89 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -222,9 +222,10 @@ pub enum CharacterAbility { range: f32, max_angle: f32, lifesteal_eff: f32, - energy_regen: f32, - energy_cost: f32, - energy_drain: f32, + energy_regen: u32, + energy_cost: u32, + energy_drain: u32, + orientation_behavior: basic_beam::OrientationBehavior, }, } @@ -1445,6 +1446,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { energy_regen, energy_cost, energy_drain, + orientation_behavior, } => CharacterState::BasicBeam(basic_beam::Data { static_data: basic_beam::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -1460,6 +1462,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { energy_cost: *energy_cost, energy_drain: *energy_drain, ability_info, + orientation_behavior: *orientation_behavior, }, timer: Duration::default(), stage_section: StageSection::Buildup, diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index 3e7028f53f..fa430ddfb3 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -10,10 +10,12 @@ use crate::{ utils::*, }, uid::Uid, + util::Dir, + Damage, DamageSource, GroupTarget, }; use serde::{Deserialize, Serialize}; use std::time::Duration; -use vek::Vec3; +use vek::*; /// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -42,7 +44,9 @@ pub struct StaticData { /// Energy consumed per second for heal ticks pub energy_cost: f32, /// Energy drained per - pub energy_drain: f32, + pub energy_drain: u32, + /// Used to dictate how orientation functions in this state + pub orientation_behavior: OrientationBehavior, /// What key is used to press ability pub ability_info: AbilityInfo, } @@ -66,6 +70,20 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); + match self.static_data.orientation_behavior { + OrientationBehavior::Normal => {}, + OrientationBehavior::Turret(speed) => { + update.ori.0 = data.inputs.look_dir; + /*update.ori.0 = Dir::new( + Quaternion::from_xyzw(update.ori.0.x, update.ori.0.y, update.ori.0.z, 0.0) + .rotated_z(data.dt.0 as f32 * speed) + .into_vec3() + .try_normalized() + .unwrap_or_default(), + );*/ + }, + } + handle_move(data, &mut update, 0.4); handle_jump(data, &mut update); if !ability_key_is_pressed(data, self.static_data.ability_info.key) { @@ -232,3 +250,9 @@ impl CharacterBehavior for Data { update } } + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum OrientationBehavior { + Normal, + Turret(f32), +} diff --git a/common/sys/src/agent.rs b/common/sys/src/agent.rs index 9684b580f2..b17169ef85 100644 --- a/common/sys/src/agent.rs +++ b/common/sys/src/agent.rs @@ -191,7 +191,7 @@ impl<'a> System<'a> for Sys { let mut inputs = &mut controller.inputs; // Default to looking in orientation direction (can be overridden below) - //inputs.look_dir = ori.0; + inputs.look_dir = ori.0; const AVG_FOLLOW_DIST: f32 = 6.0; const MAX_FOLLOW_DIST: f32 = 12.0; @@ -1378,7 +1378,14 @@ impl<'a> System<'a> for Sys { } }, Tactic::Turret => { - inputs.look_dir = ori.0; + //inputs.look_dir = ori.0; + inputs.look_dir = Dir::new( + Quaternion::from_xyzw(ori.0.x, ori.0.y, 0.0, 0.0) + .rotated_z(4.0 * dt.0 as f32) + .into_vec3() + .try_normalized() + .unwrap_or_default(), + ); if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { inputs.primary.set_state(true); diff --git a/voxygen/anim/src/object/idle.rs b/voxygen/anim/src/object/idle.rs new file mode 100644 index 0000000000..07a84d25cb --- /dev/null +++ b/voxygen/anim/src/object/idle.rs @@ -0,0 +1,32 @@ +use super::{ + super::{vek::*, Animation}, + ObjectSkeleton, SkeletonAttr, +}; +use common::comp::item::ToolKind; + +pub struct IdleAnimation; + +impl Animation for IdleAnimation { + type Dependency = (Option, Option, f64); + type Skeleton = ObjectSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"object_idle\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "object_idle")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_active_tool_kind, _second_tool_kind, _global_time): Self::Dependency, + _anim_time: f64, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2); + next.bone0.orientation = Quaternion::rotation_z(0.0); + + next + } +} diff --git a/voxygen/anim/src/object/mod.rs b/voxygen/anim/src/object/mod.rs index f7a0549663..eb484e3ad3 100644 --- a/voxygen/anim/src/object/mod.rs +++ b/voxygen/anim/src/object/mod.rs @@ -1,24 +1,18 @@ +pub mod idle; +pub mod shoot; + +// Reexports +pub use self::{idle::IdleAnimation, shoot::ShootAnimation}; + use super::{make_bone, vek::*, FigureBoneData, Skeleton}; use common::comp::{self}; +use core::convert::TryFrom; pub type Body = comp::object::Body; -#[derive(Clone, Default)] -pub struct ObjectSkeleton; - -impl<'a, Factor> Lerp for &'a ObjectSkeleton { - type Output = ObjectSkeleton; - - fn lerp_unclamped_precise(_from: Self, _to: Self, _factor: Factor) -> Self::Output { - ObjectSkeleton - } - - fn lerp_unclamped(_from: Self, _to: Self, _factor: Factor) -> Self::Output { ObjectSkeleton } -} - -pub struct SkeletonAttr; - -const SCALE: f32 = 1.0 / 11.0; +skeleton_impls!(struct ObjectSkeleton { + + bone0, +}); impl Skeleton for ObjectSkeleton { type Attr = SkeletonAttr; @@ -34,17 +28,46 @@ impl Skeleton for ObjectSkeleton { base_mat: Mat4, buf: &mut [FigureBoneData; super::MAX_BONE_COUNT], ) -> Vec3 { - buf[0] = make_bone(base_mat * Mat4::scaling_3d(SCALE)); - // TODO: Make dependent on bone, when we find an easier way to make that - // information available. - Vec3::unit_z() * 0.5 + let bone0_mat = base_mat * Mat4::::from(self.bone0); + + *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = + [make_bone(bone0_mat * Mat4::scaling_3d(1.0 / 11.0))]; + Vec3::default() + } +} + +pub struct SkeletonAttr { + bone0: (f32, f32, f32), +} + +impl<'a> std::convert::TryFrom<&'a comp::Body> for SkeletonAttr { + type Error = (); + + fn try_from(body: &'a comp::Body) -> Result { + match body { + comp::Body::Object(body) => Ok(SkeletonAttr::from(body)), + _ => Err(()), + } } } impl Default for SkeletonAttr { - fn default() -> Self { Self } + fn default() -> Self { + Self { + bone0: (0.0, 0.0, 0.0), + } + } } impl<'a> From<&'a Body> for SkeletonAttr { - fn from(_body: &'a Body) -> Self { Self } + fn from(body: &'a Body) -> Self { + use comp::object::Body::*; + Self { + bone0: match body { + CampfireLit => (2.0, 0.5, 1.0), + Pouch => (2.0, 0.5, 1.0), + _ => (0.0, 0.0, 0.0), + }, + } + } } diff --git a/voxygen/anim/src/object/shoot.rs b/voxygen/anim/src/object/shoot.rs new file mode 100644 index 0000000000..df5c015a9f --- /dev/null +++ b/voxygen/anim/src/object/shoot.rs @@ -0,0 +1,64 @@ +use super::{ + super::{vek::*, Animation}, + ObjectSkeleton, SkeletonAttr, +}; +use common::{comp::item::ToolKind, states::utils::StageSection}; +pub struct ShootAnimation; + +type ShootAnimationDependency = ( + Option, + Option, + f32, + Vec3, + Vec3, + f64, + Option, +); +impl Animation for ShootAnimation { + type Dependency = ShootAnimationDependency; + type Skeleton = ObjectSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"object_shoot\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "object_shoot")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + ( + _active_tool_kind, + _second_tool_kind, + _velocity, + orientation, + last_ori, + _global_time, + _stage_section, + ): Self::Dependency, + anim_time: f64, + rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + *rate = 1.0; + + let mut next = (*skeleton).clone(); + + let ori: Vec2 = Vec2::from(orientation); + let last_ori = Vec2::from(last_ori); + let _tilt = if ::vek::Vec2::new(ori, last_ori) + .map(|o| o.magnitude_squared()) + .map(|m| m > 0.001 && m.is_finite()) + .reduce_and() + && ori.angle_between(last_ori).is_finite() + { + ori.angle_between(last_ori).min(0.2) + * last_ori.determine_side(Vec2::zero(), ori).signum() + } else { + 0.0 + } * 1.3; + + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2); + next.bone0.orientation = Quaternion::rotation_z(anim_time as f32 * 1.0); + + next + } +} diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index f1717bd619..d9361cf0a6 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -3472,7 +3472,7 @@ impl FigureMgr { ); }, Body::Object(body) => { - let (model, _) = self.object_model_cache.get_or_create_model( + let (model, skeleton_attr) = self.object_model_cache.get_or_create_model( renderer, &mut self.col_lights, *body, @@ -3488,6 +3488,72 @@ impl FigureMgr { FigureState::new(renderer, ObjectSkeleton::default()) }); + let (character, last_character) = match (character, last_character) { + (Some(c), Some(l)) => (c, l), + _ => continue, + }; + + if !character.same_variant(&last_character.0) { + state.state_time = 0.0; + } + + let target_base = match ( + physics.on_ground, + vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + physics.in_liquid.is_some(), // In water + ) { + // Standing + (true, false, false) => anim::object::IdleAnimation::update_skeleton( + &ObjectSkeleton::default(), + (active_tool_kind, second_tool_kind, time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + _ => anim::object::IdleAnimation::update_skeleton( + &ObjectSkeleton::default(), + (active_tool_kind, second_tool_kind, time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + }; + + let target_bones = match &character { + CharacterState::BasicRanged(s) => { + 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() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f64() + }, + + _ => 0.0, + }; + anim::object::ShootAnimation::update_skeleton( + &target_base, + ( + active_tool_kind, + second_tool_kind, + vel.0.magnitude(), + ori, + state.last_ori, + time, + Some(s.stage_section), + ), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + // TODO! + _ => target_base, + }; + + state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, pos.0,