This commit is contained in:
Sam 2024-02-19 12:59:42 -05:00
parent cc70685f7a
commit 89048e9530
33 changed files with 292 additions and 168 deletions

View File

@ -197,7 +197,7 @@
secondary: Simple(None, "common.abilities.hammer.wide_wallop"), secondary: Simple(None, "common.abilities.hammer.wide_wallop"),
abilities: [ abilities: [
Simple(Hammer(ScornfulSwipe), "common.abilities.hammer.scornful_swipe"), Simple(Hammer(ScornfulSwipe), "common.abilities.hammer.scornful_swipe"),
// Simple(Hammer(Tremor), "common.abilities.hammer.tremor"), Simple(Hammer(Tremor), "common.abilities.hammer.tremor"),
// Simple(Hammer(VigorousBash), "common.abilities.hammer.vigorous_bash"), // Simple(Hammer(VigorousBash), "common.abilities.hammer.vigorous_bash"),
// Simple(Hammer(Retaliate), "common.abilities.hammer.retaliate"), // Simple(Hammer(Retaliate), "common.abilities.hammer.retaliate"),
// Simple(Hammer(SpineCracker), "common.abilities.hammer.spine_cracker"), // Simple(Hammer(SpineCracker), "common.abilities.hammer.spine_cracker"),

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Energy, damage_kind: Energy,
specifier: Fire, specifier: Fire,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: Value(0.3), strength: Value(0.3),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Water, specifier: Water,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Piercing, damage_kind: Piercing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Steam, specifier: Steam,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Fire, specifier: Fire,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Water, specifier: Water,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: DamageFraction(0.3), strength: DamageFraction(0.3),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: Value(0.3), strength: Value(0.3),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: Value(3.0), strength: Value(3.0),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: DamageFraction(0.6), strength: DamageFraction(0.6),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: DamageFraction(0.06), strength: DamageFraction(0.06),
chance: 1, chance: 1,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,5 +21,7 @@ Shockwave(
strength: Value(0.3), strength: Value(0.3),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Water, specifier: Water,
ori_rate: 0.0, ori_rate: 0.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,5 +21,7 @@ Shockwave(
strength: DamageFraction(0.3), strength: DamageFraction(0.3),
chance: 1.0, chance: 1.0,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Crushing, damage_kind: Crushing,
specifier: Ground, specifier: Ground,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -21,4 +21,6 @@ Shockwave(
strength: DamageFraction(0.1), strength: DamageFraction(0.1),
chance: 0.1, chance: 0.1,
))), ))),
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Energy, damage_kind: Energy,
specifier: Fire, specifier: Fire,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

View File

@ -0,0 +1,20 @@
Shockwave(
energy_cost: 0,
buildup_duration: 0.4,
swing_duration: 0.3,
recover_duration: 0.3,
damage: 30,
poise_damage: 40,
knockback: (strength: 15.0, direction: Up),
shockwave_angle: 60.0,
shockwave_vertical_angle: 45.0,
shockwave_speed: 10.0,
shockwave_duration: 1.5,
dodgeable: Jump,
move_efficiency: 0.0,
damage_kind: Crushing,
specifier: Ground,
ori_rate: 0.2,
timing: PostAction,
emit_outcome: false,
)

View File

@ -15,4 +15,6 @@ Shockwave(
damage_kind: Energy, damage_kind: Energy,
specifier: Fire, specifier: Fire,
ori_rate: 1.0, ori_rate: 1.0,
timing: PostBuildup,
emit_outcome: true,
) )

BIN
assets/voxygen/element/skills/hammer/tremor.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -386,3 +386,6 @@ common-abilities-hammer-wide_wallop = Wide Wallop
common-abilities-hammer-scornful_swipe = Scornful Swipe common-abilities-hammer-scornful_swipe = Scornful Swipe
.desc = .desc =
Bolster your fortitude and stamina by taunting your enemies before striking at them. If you fall to an enemy they become empowered. Bolster your fortitude and stamina by taunting your enemies before striking at them. If you fall to an enemy they become empowered.
common-abilities-hammer-tremor = Tremor
.desc =
Strike the earth with enough force that the ground beneath your foes trembles.

View File

@ -933,6 +933,8 @@ pub enum CharacterAbility {
specifier: comp::shockwave::FrontendSpecifier, specifier: comp::shockwave::FrontendSpecifier,
ori_rate: f32, ori_rate: f32,
damage_effect: Option<CombatEffect>, damage_effect: Option<CombatEffect>,
timing: shockwave::Timing,
emit_outcome: bool,
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
}, },
@ -1532,6 +1534,8 @@ impl CharacterAbility {
specifier: _, specifier: _,
ori_rate: _, ori_rate: _,
ref mut damage_effect, ref mut damage_effect,
timing: _,
emit_outcome: _,
meta: _, meta: _,
} => { } => {
*buildup_duration /= stats.speed; *buildup_duration /= stats.speed;
@ -2572,6 +2576,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
specifier, specifier,
ori_rate, ori_rate,
damage_effect, damage_effect,
timing,
emit_outcome,
meta: _, meta: _,
} => CharacterState::Shockwave(shockwave::Data { } => CharacterState::Shockwave(shockwave::Data {
static_data: shockwave::StaticData { static_data: shockwave::StaticData {
@ -2592,6 +2598,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
damage_kind: *damage_kind, damage_kind: *damage_kind,
specifier: *specifier, specifier: *specifier,
ori_rate: *ori_rate, ori_rate: *ori_rate,
timing: *timing,
emit_outcome: *emit_outcome,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,

View File

@ -53,8 +53,12 @@ pub struct StaticData {
pub damage_kind: DamageKind, pub damage_kind: DamageKind,
/// Used to specify the shockwave to the frontend /// Used to specify the shockwave to the frontend
pub specifier: shockwave::FrontendSpecifier, pub specifier: shockwave::FrontendSpecifier,
/// Controls outcome emission
pub emit_outcome: bool,
/// How fast enemy can rotate /// How fast enemy can rotate
pub ori_rate: f32, pub ori_rate: f32,
/// Timing of shockwave
pub timing: Timing,
} }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -85,6 +89,111 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Attack // Attack
if matches!(self.static_data.timing, Timing::PostBuildup) {
self.attack(data, output_events);
}
// Transitions to swing
update.character = CharacterState::Shockwave(Data {
timer: Duration::default(),
stage_section: StageSection::Action,
..*self
});
}
},
StageSection::Action => {
if self.timer < self.static_data.swing_duration {
// Swings
update.character = CharacterState::Shockwave(Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
// Send local event used for frontend shenanigans
if self.static_data.emit_outcome {
match self.static_data.specifier {
shockwave::FrontendSpecifier::IceSpikes => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::FlashFreeze {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
shockwave::FrontendSpecifier::Ground => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::GroundSlam {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
shockwave::FrontendSpecifier::Steam => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::Steam {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
shockwave::FrontendSpecifier::Fire => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::FireShockwave {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
_ => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::Swoosh {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
}
}
} else {
// Attack
if matches!(self.static_data.timing, Timing::PostAction) {
self.attack(data, output_events);
}
// Transitions to recover
update.character = CharacterState::Shockwave(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
// Recovers
update.character = CharacterState::Shockwave(Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
} else {
// Done
end_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
},
}
// At end of state logic so an interrupt isn't overwritten
handle_interrupts(data, &mut update, output_events);
update
}
}
impl Data {
fn attack(&self, data: &JoinData, output_events: &mut OutputEvents) {
let poise = AttackEffect::new( let poise = AttackEffect::new(
Some(GroupTarget::OutOfGroup), Some(GroupTarget::OutOfGroup),
CombatEffect::Poise(self.static_data.poise_damage), CombatEffect::Poise(self.static_data.poise_damage),
@ -129,89 +238,11 @@ impl CharacterBehavior for Data {
pos: *data.pos, pos: *data.pos,
ori: *data.ori, ori: *data.ori,
}); });
// Transitions to swing
update.character = CharacterState::Shockwave(Data {
timer: Duration::default(),
stage_section: StageSection::Action,
..*self
});
}
},
StageSection::Action => {
if self.timer < self.static_data.swing_duration {
// Swings
update.character = CharacterState::Shockwave(Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
// Send local event used for frontend shenanigans
match self.static_data.specifier {
shockwave::FrontendSpecifier::IceSpikes => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::FlashFreeze {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
shockwave::FrontendSpecifier::Ground => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::GroundSlam {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
shockwave::FrontendSpecifier::Steam => {
output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Steam {
pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
}));
},
shockwave::FrontendSpecifier::Fire => {
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::FireShockwave {
pos: data.pos.0
+ *data.ori.look_dir() * (data.body.max_radius()),
},
));
},
_ => {
output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Swoosh {
pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
}));
},
}
} else {
// Transitions to recover
update.character = CharacterState::Shockwave(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
// Recovers
update.character = CharacterState::Shockwave(Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
} else {
// Done
end_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
},
}
// At end of state logic so an interrupt isn't overwritten
handle_interrupts(data, &mut update, output_events);
update
} }
} }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Timing {
PostBuildup,
PostAction,
}

View File

@ -1,11 +1,8 @@
use super::{ use super::{
super::{vek::*, Animation}, super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr, hammer_start, twist_back, twist_forward, CharacterSkeleton, SkeletonAttr,
};
use common::{
comp::item::Hands,
states::utils::{AbilityInfo, StageSection},
}; };
use common::states::utils::StageSection;
pub struct Input { pub struct Input {
pub attack: bool, pub attack: bool,
@ -13,13 +10,7 @@ pub struct Input {
pub struct ShockwaveAnimation; pub struct ShockwaveAnimation;
impl Animation for ShockwaveAnimation { impl Animation for ShockwaveAnimation {
type Dependency<'a> = ( type Dependency<'a> = (Option<&'a str>, f32, f32, Option<StageSection>);
Option<AbilityInfo>,
(Option<Hands>, Option<Hands>),
f32,
f32,
Option<StageSection>,
);
type Skeleton = CharacterSkeleton; type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")] #[cfg(feature = "use-dyn-lib")]
@ -28,7 +19,7 @@ impl Animation for ShockwaveAnimation {
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_shockwave")] #[cfg_attr(feature = "be-dyn-lib", export_name = "character_shockwave")]
fn update_skeleton_inner( fn update_skeleton_inner(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
(_ability_info, hands, _global_time, velocity, stage_section): Self::Dependency<'_>, (ability_id, _global_time, velocity, stage_section): Self::Dependency<'_>,
anim_time: f32, anim_time: f32,
rate: &mut f32, rate: &mut f32,
s_a: &SkeletonAttr, s_a: &SkeletonAttr,
@ -36,6 +27,11 @@ impl Animation for ShockwaveAnimation {
*rate = 1.0; *rate = 1.0;
let mut next = (*skeleton).clone(); let mut next = (*skeleton).clone();
if matches!(stage_section, Some(StageSection::Action)) {
next.main_weapon_trail = true;
next.off_weapon_trail = true;
}
let (move1, move2, move3) = match stage_section { let (move1, move2, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time, 0.0, 0.0), Some(StageSection::Buildup) => (anim_time, 0.0, 0.0),
Some(StageSection::Action) => (1.0, anim_time, 0.0), Some(StageSection::Action) => (1.0, anim_time, 0.0),
@ -43,13 +39,12 @@ impl Animation for ShockwaveAnimation {
_ => (0.0, 0.0, 0.0), _ => (0.0, 0.0, 0.0),
}; };
if matches!( match ability_id {
stage_section, Some(
Some(StageSection::Action | StageSection::Recover) "common.abilities.staff.fireshockwave"
) { | "common.abilities.sceptre.healingaura"
next.main_weapon_trail = true; | "common.abilities.sceptre.wardingaura",
next.off_weapon_trail = true; ) => {
}
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2); next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2);
@ -113,9 +108,35 @@ impl Animation for ShockwaveAnimation {
next.foot_r.orientation = Quaternion::rotation_y(move1 * -0.3 + move2 * 0.3) next.foot_r.orientation = Quaternion::rotation_y(move1 * -0.3 + move2 * 0.3)
* Quaternion::rotation_z(move1 * 0.4 + move2 * -0.4); * Quaternion::rotation_z(move1 * 0.4 + move2 * -0.4);
} }
},
Some("common.abilities.hammer.tremor") => {
hammer_start(&mut next, s_a);
let (move1, move2, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time, 0.0, 0.0),
Some(StageSection::Action) => (1.0, anim_time, 0.0),
Some(StageSection::Recover) => (1.0, 1.0, anim_time),
_ => (0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1 * pullback;
let move2 = move2 * pullback;
if let (None, Some(Hands::Two)) = hands { twist_back(&mut next, move1, 1.4, 0.7, 0.5, 0.9);
next.second = next.main; next.foot_l.orientation.rotate_z(move1 * 1.4);
next.foot_l.position += Vec3::new(-1.0, -3.0, 0.0) * move1;
next.control.orientation.rotate_x(move1 * 2.6);
next.control.orientation.rotate_y(move1 * 0.8);
twist_forward(&mut next, move2, 2.1, 1.2, 0.9, 1.6);
next.foot_l.orientation.rotate_z(move2 * -1.4);
next.foot_l.position += Vec3::new(2.0, 7.0, 0.0) * move2;
next.control.orientation.rotate_z(move2 * 2.1);
next.control.orientation.rotate_x(move2 * -2.0);
next.control.orientation.rotate_z(move2 * 1.2);
next.control.position += Vec3::new(-16.0, 0.0, 0.0) * move2;
next.chest.orientation.rotate_x(-0.8 * move2);
},
_ => {},
} }
next next

View File

@ -318,6 +318,7 @@ image_ids! {
hammer_solid_smash: "voxygen.element.skills.hammer.solid_smash", hammer_solid_smash: "voxygen.element.skills.hammer.solid_smash",
hammer_wide_wallop: "voxygen.element.skills.hammer.wide_wallop", hammer_wide_wallop: "voxygen.element.skills.hammer.wide_wallop",
hammer_scornful_swipe: "voxygen.element.skills.hammer.scornful_swipe", hammer_scornful_swipe: "voxygen.element.skills.hammer.scornful_swipe",
hammer_tremor: "voxygen.element.skills.hammer.tremor",
// Skilltree Icons // Skilltree Icons
health_plus_skill: "voxygen.element.skills.skilltree.health_plus", health_plus_skill: "voxygen.element.skills.skilltree.health_plus",
energy_plus_skill: "voxygen.element.skills.skilltree.energy_plus", energy_plus_skill: "voxygen.element.skills.skilltree.energy_plus",

View File

@ -625,6 +625,7 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
"common.abilities.hammer.solid_smash" => imgs.hammer_solid_smash, "common.abilities.hammer.solid_smash" => imgs.hammer_solid_smash,
"common.abilities.hammer.wide_wallop" => imgs.hammer_wide_wallop, "common.abilities.hammer.wide_wallop" => imgs.hammer_wide_wallop,
"common.abilities.hammer.scornful_swipe" => imgs.hammer_scornful_swipe, "common.abilities.hammer.scornful_swipe" => imgs.hammer_scornful_swipe,
"common.abilities.hammer.tremor" => imgs.hammer_tremor,
// Bow // Bow
"common.abilities.bow.charged" => imgs.bow_m1, "common.abilities.bow.charged" => imgs.bow_m1,
"common.abilities.bow.repeater" => imgs.bow_m2, "common.abilities.bow.repeater" => imgs.bow_m2,

View File

@ -1652,13 +1652,7 @@ impl FigureMgr {
}; };
anim::character::ShockwaveAnimation::update_skeleton( anim::character::ShockwaveAnimation::update_skeleton(
&target_base, &target_base,
( (ability_id, time, rel_vel.magnitude(), Some(s.stage_section)),
Some(s.static_data.ability_info),
hands,
time,
rel_vel.magnitude(),
Some(s.stage_section),
),
stage_progress, stage_progress,
&mut state_animation_rate, &mut state_animation_rate,
skeleton_attr, skeleton_attr,
@ -1678,15 +1672,11 @@ impl FigureMgr {
}, },
_ => 0.0, _ => 0.0,
}; };
// ? Aura confirmed just shockwave
anim::character::ShockwaveAnimation::update_skeleton( anim::character::ShockwaveAnimation::update_skeleton(
&target_base, &target_base,
( (ability_id, time, rel_vel.magnitude(), Some(s.stage_section)),
Some(s.static_data.ability_info),
hands,
time,
rel_vel.magnitude(),
Some(s.stage_section),
),
stage_progress, stage_progress,
&mut state_animation_rate, &mut state_animation_rate,
skeleton_attr, skeleton_attr,