From ea8744c4a196bc667366cbad41373de6e3fd6b10 Mon Sep 17 00:00:00 2001 From: Justin Shipsey Date: Tue, 25 Feb 2020 06:47:56 +0000 Subject: [PATCH 01/22] animation housekeeping --- voxygen/src/anim/bird_medium/idle.rs | 8 +-- voxygen/src/anim/bird_medium/mod.rs | 8 +-- voxygen/src/anim/bird_medium/run.rs | 12 ++-- voxygen/src/anim/character/run.rs | 73 +++++++++++------------- voxygen/src/anim/character/stand.rs | 44 ++++++-------- voxygen/src/anim/quadruped_small/idle.rs | 27 +++++---- voxygen/src/anim/quadruped_small/run.rs | 41 ++++++------- 7 files changed, 100 insertions(+), 113 deletions(-) diff --git a/voxygen/src/anim/bird_medium/idle.rs b/voxygen/src/anim/bird_medium/idle.rs index 82cd25c2bc..86b776720e 100644 --- a/voxygen/src/anim/bird_medium/idle.rs +++ b/voxygen/src/anim/bird_medium/idle.rs @@ -33,7 +33,7 @@ impl Animation for IdleAnimation { * 0.25, ); - next.head.offset = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1) / 11.0; + next.head.offset = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1); next.head.ori = Quaternion::rotation_z(duck_head_look.x) * Quaternion::rotation_x(-duck_head_look.y.abs() + wave_slow_cos * 0.03); next.head.scale = Vec3::one(); @@ -46,7 +46,7 @@ impl Animation for IdleAnimation { next.torso.ori = Quaternion::rotation_y(wave_slow * 0.03); next.torso.scale = Vec3::one() / 11.0; - next.tail.offset = Vec3::new(0.0, skeleton_attr.tail.0, skeleton_attr.tail.1) / 11.0; + next.tail.offset = Vec3::new(0.0, skeleton_attr.tail.0, skeleton_attr.tail.1); next.tail.ori = Quaternion::rotation_x(wave_slow_cos * 0.03); next.tail.scale = Vec3::one(); @@ -54,7 +54,7 @@ impl Animation for IdleAnimation { -skeleton_attr.wing.0, skeleton_attr.wing.1, skeleton_attr.wing.2, - ) / 11.0; + ); next.wing_l.ori = Quaternion::rotation_z(0.0); next.wing_l.scale = Vec3::one() * 1.05; @@ -62,7 +62,7 @@ impl Animation for IdleAnimation { skeleton_attr.wing.0, skeleton_attr.wing.1, skeleton_attr.wing.2, - ) / 11.0; + ); next.wing_r.ori = Quaternion::rotation_y(0.0); next.wing_r.scale = Vec3::one() * 1.05; diff --git a/voxygen/src/anim/bird_medium/mod.rs b/voxygen/src/anim/bird_medium/mod.rs index f1330d82c8..0ff89377ae 100644 --- a/voxygen/src/anim/bird_medium/mod.rs +++ b/voxygen/src/anim/bird_medium/mod.rs @@ -31,11 +31,11 @@ impl Skeleton for BirdMediumSkeleton { let torso_mat = self.torso.compute_base_matrix(); [ - FigureBoneData::new(self.head.compute_base_matrix() * torso_mat), + FigureBoneData::new(torso_mat * self.head.compute_base_matrix()), FigureBoneData::new(torso_mat), - FigureBoneData::new(self.tail.compute_base_matrix() * torso_mat), - FigureBoneData::new(self.wing_l.compute_base_matrix() * torso_mat), - FigureBoneData::new(self.wing_r.compute_base_matrix() * torso_mat), + FigureBoneData::new(torso_mat * self.tail.compute_base_matrix()), + FigureBoneData::new(torso_mat * self.wing_l.compute_base_matrix()), + FigureBoneData::new(torso_mat * self.wing_r.compute_base_matrix()), FigureBoneData::new(self.leg_l.compute_base_matrix()), FigureBoneData::new(self.leg_r.compute_base_matrix()), FigureBoneData::default(), diff --git a/voxygen/src/anim/bird_medium/run.rs b/voxygen/src/anim/bird_medium/run.rs index 36cb77cc50..ea73668941 100644 --- a/voxygen/src/anim/bird_medium/run.rs +++ b/voxygen/src/anim/bird_medium/run.rs @@ -28,7 +28,7 @@ impl Animation for RunAnimation { 0.0, skeleton_attr.head.0, skeleton_attr.head.1 + center * 0.5, - ) / 11.0; + ); next.head.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0 + center * 0.03); next.head.scale = Vec3::one(); @@ -44,7 +44,7 @@ impl Animation for RunAnimation { 0.0, skeleton_attr.tail.0, skeleton_attr.tail.1 + centeroffset * 0.6, - ) / 11.0; + ); next.tail.ori = Quaternion::rotation_x(center * 0.03); next.tail.scale = Vec3::one(); @@ -52,16 +52,16 @@ impl Animation for RunAnimation { -skeleton_attr.wing.0, skeleton_attr.wing.1, skeleton_attr.wing.2, - ) / 11.0; - next.wing_l.ori = Quaternion::rotation_y(footl * 0.3); + ); + next.wing_l.ori = Quaternion::rotation_y(footl * 0.1); next.wing_l.scale = Vec3::one() * 1.05; next.wing_r.offset = Vec3::new( skeleton_attr.wing.0, skeleton_attr.wing.1, skeleton_attr.wing.2, - ) / 11.0; - next.wing_r.ori = Quaternion::rotation_y(footr * 0.3); + ); + next.wing_r.ori = Quaternion::rotation_y(footr * 0.1); next.wing_r.scale = Vec3::one() * 1.05; next.leg_l.offset = Vec3::new( diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index f202ef5545..c4e2479248 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -21,22 +21,18 @@ impl Animation for RunAnimation { let speed = Vec2::::from(velocity).magnitude(); *rate = speed; - let constant = 1.0; - let wave = (((5.0) - / (1.1 + 3.9 * ((anim_time as f32 * constant as f32 * 1.2).sin()).powf(2.0 as f32))) + let lab = 1.0; + let long = (((5.0) + / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 1.2).sin()).powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * constant as f32 * 1.2).sin()); - let wave_cos = (((5.0) - / (1.1 + 3.9 * ((anim_time as f32 * constant as f32 * 2.4).sin()).powf(2.0 as f32))) + * ((anim_time as f32 * lab as f32 * 1.2).sin()); + let short = (((5.0) + / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 2.4).sin()).powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * constant as f32 * 1.5).sin()); - let wave_cos_dub = (((5.0) - / (1.1 + 3.9 * ((anim_time as f32 * constant as f32 * 4.8).sin()).powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * constant as f32 * 1.5).sin()); + * ((anim_time as f32 * lab as f32 * 1.5).sin()); - let wave_diff = (anim_time as f32 * 0.6).sin(); let wave_stop = (anim_time as f32 * 2.6).min(PI / 2.0).sin(); + let head_look = Vec2::new( ((global_time + anim_time) as f32 / 4.0) .floor() @@ -67,48 +63,48 @@ impl Animation for RunAnimation { next.head.offset = Vec3::new( 0.0, -3.0 + skeleton_attr.neck_forward, - skeleton_attr.neck_height + 20.0 + wave_cos * 1.3, + skeleton_attr.neck_height + 20.0 + short * 1.3, ); - next.head.ori = Quaternion::rotation_z(head_look.x + wave * 0.1) + next.head.ori = Quaternion::rotation_z(head_look.x + long * 0.1) * Quaternion::rotation_x(head_look.y + 0.35); next.head.scale = Vec3::one() * skeleton_attr.head_scale; - next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + wave_cos * 1.1); - next.chest.ori = Quaternion::rotation_z(wave * 0.2); + next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + short * 1.1); + next.chest.ori = Quaternion::rotation_z(long * 0.2); next.chest.scale = Vec3::one(); - next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + wave_cos * 1.1); - next.belt.ori = Quaternion::rotation_z(wave * 0.35); + next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + short * 1.1); + next.belt.ori = Quaternion::rotation_z(long * 0.35); next.belt.scale = Vec3::one(); - next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + wave_cos * 1.1); - next.shorts.ori = Quaternion::rotation_z(wave * 0.6); + next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + short * 1.1); + next.shorts.ori = Quaternion::rotation_z(long * 0.6); next.shorts.scale = Vec3::one(); next.l_hand.offset = Vec3::new( -6.0 + wave_stop * -1.0, - -0.25 + wave_cos * 2.0, - 5.0 - wave * 1.5, + -0.25 + short * 2.0, + 5.0 - long * 1.5, ); next.l_hand.ori = - Quaternion::rotation_x(0.8 + wave_cos * 1.2) * Quaternion::rotation_y(wave_stop * 0.1); + Quaternion::rotation_x(0.8 + short * 1.2) * Quaternion::rotation_y(wave_stop * 0.1); next.l_hand.scale = Vec3::one(); next.r_hand.offset = Vec3::new( 6.0 + wave_stop * 1.0, - -0.25 - wave_cos * 2.0, - 5.0 + wave * 1.5, + -0.25 + short * -2.0, + 5.0 + long * 1.5, ); - next.r_hand.ori = Quaternion::rotation_x(0.8 + wave_cos * -1.2) - * Quaternion::rotation_y(wave_stop * -0.1); + next.r_hand.ori = + Quaternion::rotation_x(0.8 + short * -1.2) * Quaternion::rotation_y(wave_stop * -0.1); next.r_hand.scale = Vec3::one(); - next.l_foot.offset = Vec3::new(-3.4, 0.0 + wave_cos * 1.0, 6.0 - wave_cos_dub * 0.7); - next.l_foot.ori = Quaternion::rotation_x(-0.0 - wave_cos * 1.2); + next.l_foot.offset = Vec3::new(-3.4, 0.0 + short * 1.0, 6.0); + next.l_foot.ori = Quaternion::rotation_x(-0.0 - short * 1.2); next.l_foot.scale = Vec3::one(); - next.r_foot.offset = Vec3::new(3.4, 0.0 - wave_cos * 1.0, 6.0 - wave_cos_dub * 0.7); - next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave_cos * 1.2); + next.r_foot.offset = Vec3::new(3.4, short * -1.0, 6.0); + next.r_foot.ori = Quaternion::rotation_x(short * 1.2); next.r_foot.scale = Vec3::one(); next.main.offset = Vec3::new( @@ -116,16 +112,15 @@ impl Animation for RunAnimation { -5.0 + skeleton_attr.weapon_y, 15.0, ); - next.main.ori = - Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_cos * 0.25); + next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + short * 0.25); next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); - next.l_shoulder.ori = Quaternion::rotation_x(wave_cos * 0.15); + next.l_shoulder.offset = Vec3::new(-5.0, -0.5, 4.7); + next.l_shoulder.ori = Quaternion::rotation_x(short * 0.15); next.l_shoulder.scale = Vec3::one() * 1.1; - next.r_shoulder.offset = Vec3::new(5.0, 0.0, 4.7); - next.r_shoulder.ori = Quaternion::rotation_x(wave * 0.15); + next.r_shoulder.offset = Vec3::new(5.0, -0.5, 4.7); + next.r_shoulder.ori = Quaternion::rotation_x(long * 0.15); next.r_shoulder.scale = Vec3::one() * 1.1; next.glider.offset = Vec3::new(0.0, 5.0, 0.0); @@ -136,9 +131,9 @@ impl Animation for RunAnimation { next.lantern.ori = Quaternion::rotation_y(0.0); next.lantern.scale = Vec3::one() * 0.0; - next.torso.offset = Vec3::new(0.0, -0.3 + wave * -0.08, 0.4) * skeleton_attr.scaler; + next.torso.offset = Vec3::new(0.0, -0.3 + long * -0.08, 0.4) * skeleton_attr.scaler; next.torso.ori = - Quaternion::rotation_x(wave_stop * speed * -0.06 + wave_diff * speed * -0.005) + Quaternion::rotation_x(wave_stop * speed * -0.06 + wave_stop * speed * -0.005) * Quaternion::rotation_y(tilt); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; diff --git a/voxygen/src/anim/character/stand.rs b/voxygen/src/anim/character/stand.rs index 25459e7d3c..2116115be7 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -1,6 +1,6 @@ use super::{super::Animation, CharacterSkeleton, SkeletonAttr}; use common::comp::item::Tool; -use std::{f32::consts::PI, ops::Mul}; +use std::ops::Mul; use vek::*; pub struct StandAnimation; @@ -18,9 +18,8 @@ impl Animation for StandAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let wave_ultra_slow = (anim_time as f32 * 1.0 + PI).sin(); - let wave_ultra_slow_cos = (anim_time as f32 * 1.0 + PI).cos(); - let wave_ultra_slow_abs = ((anim_time as f32 * 0.5 + PI).sin()) + 1.0; + let slow = (anim_time as f32 * 1.0).sin(); + let breathe = ((anim_time as f32 * 0.5).sin()).abs(); let head_look = Vec2::new( ((global_time + anim_time) as f32 / 12.0) @@ -37,40 +36,33 @@ impl Animation for StandAnimation { next.head.offset = Vec3::new( 0.0 + skeleton_attr.neck_right, -3.0 + skeleton_attr.neck_forward, - skeleton_attr.neck_height + 21.0 + wave_ultra_slow * 0.3, + skeleton_attr.neck_height + 21.0 + slow * 0.3, ); next.head.ori = Quaternion::rotation_z(head_look.x) * Quaternion::rotation_x(head_look.y.abs()); next.head.scale = Vec3::one() * skeleton_attr.head_scale; - next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + wave_ultra_slow * 0.3); + next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + slow * 0.3); next.chest.ori = Quaternion::rotation_x(0.0); - next.chest.scale = Vec3::one() * 1.01 + wave_ultra_slow_abs * 0.05; + next.chest.scale = Vec3::one() * 1.01 + breathe * 0.05; - next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + wave_ultra_slow * 0.3); + next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + slow * 0.3); next.belt.ori = Quaternion::rotation_x(0.0); - next.belt.scale = Vec3::one() + wave_ultra_slow_abs * 0.05; + next.belt.scale = Vec3::one() + breathe * 0.05; - next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + wave_ultra_slow * 0.3); + next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + slow * 0.3); next.shorts.ori = Quaternion::rotation_x(0.0); next.shorts.scale = Vec3::one(); - next.l_hand.offset = Vec3::new( - -6.0, - -0.25 + wave_ultra_slow_cos * 0.15, - 5.0 + wave_ultra_slow * 0.5, - ); + next.l_hand.offset = Vec3::new(-6.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5); - next.l_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * -0.06); + next.l_hand.ori = Quaternion::rotation_x(0.0 + slow * -0.06); next.l_hand.scale = Vec3::one(); - next.r_hand.offset = Vec3::new( - 6.0, - -0.25 + wave_ultra_slow_cos * 0.15, - 5.0 + wave_ultra_slow * 0.5 + wave_ultra_slow_abs * -0.05, - ); - next.r_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * -0.06); - next.r_hand.scale = Vec3::one() + wave_ultra_slow_abs * -0.05; + next.r_hand.offset = + Vec3::new(6.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5 + breathe * -0.05); + next.r_hand.ori = Quaternion::rotation_x(0.0 + slow * -0.06); + next.r_hand.scale = Vec3::one() + breathe * -0.05; next.l_foot.offset = Vec3::new(-3.4, -0.1, 8.0); next.l_foot.ori = Quaternion::identity(); @@ -86,15 +78,15 @@ impl Animation for StandAnimation { 15.0, ); next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one() + wave_ultra_slow_abs * -0.05; + next.main.scale = Vec3::one() + breathe * -0.05; next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 5.0); next.l_shoulder.ori = Quaternion::rotation_x(0.0); - next.l_shoulder.scale = (Vec3::one() + wave_ultra_slow_abs * -0.05) * 1.15; + next.l_shoulder.scale = (Vec3::one() + breathe * -0.05) * 1.15; next.r_shoulder.offset = Vec3::new(5.0, 0.0, 5.0); next.r_shoulder.ori = Quaternion::rotation_x(0.0); - next.r_shoulder.scale = (Vec3::one() + wave_ultra_slow_abs * -0.05) * 1.15; + next.r_shoulder.scale = (Vec3::one() + breathe * -0.05) * 1.15; next.glider.offset = Vec3::new(0.0, 5.0, 0.0); next.glider.ori = Quaternion::rotation_y(0.0); diff --git a/voxygen/src/anim/quadruped_small/idle.rs b/voxygen/src/anim/quadruped_small/idle.rs index 9293d7cec7..59a3c2fc0c 100644 --- a/voxygen/src/anim/quadruped_small/idle.rs +++ b/voxygen/src/anim/quadruped_small/idle.rs @@ -17,11 +17,10 @@ impl Animation for IdleAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let wave = (anim_time as f32 * 14.0).sin(); - let wave_slow = (anim_time as f32 * 3.5 + PI).sin(); - let wave_slow_cos = (anim_time as f32 * 3.5 + PI).cos(); + let slow = (anim_time as f32 * 3.5).sin(); + let slow_alt = (anim_time as f32 * 3.5 + PI).sin(); - let pig_head_look = Vec2::new( + let head_look = Vec2::new( ((global_time + anim_time) as f32 / 8.0) .floor() .mul(7331.0) @@ -35,17 +34,17 @@ impl Animation for IdleAnimation { ); next.head.offset = - Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + wave * 0.2) / 11.0; - next.head.ori = Quaternion::rotation_z(pig_head_look.x) - * Quaternion::rotation_x(pig_head_look.y + wave_slow_cos * 0.03); + Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + slow * 0.2) / 11.0; + next.head.ori = Quaternion::rotation_z(head_look.x) + * Quaternion::rotation_x(head_look.y + slow_alt * 0.03); next.head.scale = Vec3::one() / 10.5; next.chest.offset = Vec3::new( - wave_slow * 0.05, + slow * 0.05, skeleton_attr.chest.0, - skeleton_attr.chest.1 + wave_slow_cos * 0.2, + skeleton_attr.chest.1 + slow_alt * 0.2, ) / 11.0; - next.chest.ori = Quaternion::rotation_y(wave_slow * 0.05); + next.chest.ori = Quaternion::rotation_y(slow * 0.05); next.chest.scale = Vec3::one() / 11.0; next.leg_lf.offset = Vec3::new( @@ -53,7 +52,7 @@ impl Animation for IdleAnimation { skeleton_attr.feet_f.1, skeleton_attr.feet_f.2, ) / 11.0; - next.leg_lf.ori = Quaternion::rotation_x(wave_slow * 0.08); + next.leg_lf.ori = Quaternion::rotation_x(slow * 0.08); next.leg_lf.scale = Vec3::one() / 11.0; next.leg_rf.offset = Vec3::new( @@ -61,7 +60,7 @@ impl Animation for IdleAnimation { skeleton_attr.feet_f.1, skeleton_attr.feet_f.2, ) / 11.0; - next.leg_rf.ori = Quaternion::rotation_x(wave_slow_cos * 0.08); + next.leg_rf.ori = Quaternion::rotation_x(slow_alt * 0.08); next.leg_rf.scale = Vec3::one() / 11.0; next.leg_lb.offset = Vec3::new( @@ -69,7 +68,7 @@ impl Animation for IdleAnimation { skeleton_attr.feet_b.1, skeleton_attr.feet_b.2, ) / 11.0; - next.leg_lb.ori = Quaternion::rotation_x(wave_slow_cos * 0.08); + next.leg_lb.ori = Quaternion::rotation_x(slow_alt * 0.08); next.leg_lb.scale = Vec3::one() / 11.0; next.leg_rb.offset = Vec3::new( @@ -77,7 +76,7 @@ impl Animation for IdleAnimation { skeleton_attr.feet_b.1, skeleton_attr.feet_b.2, ) / 11.0; - next.leg_rb.ori = Quaternion::rotation_x(wave_slow * 0.08); + next.leg_rb.ori = Quaternion::rotation_x(slow * 0.08); next.leg_rb.scale = Vec3::one() / 11.0; next diff --git a/voxygen/src/anim/quadruped_small/run.rs b/voxygen/src/anim/quadruped_small/run.rs index 38a7cfeb58..801c66ddb4 100644 --- a/voxygen/src/anim/quadruped_small/run.rs +++ b/voxygen/src/anim/quadruped_small/run.rs @@ -1,4 +1,5 @@ use super::{super::Animation, QuadrupedSmallSkeleton, SkeletonAttr}; +use std::f32::consts::PI; use vek::*; pub struct RunAnimation; @@ -16,55 +17,55 @@ impl Animation for RunAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let wave = (anim_time as f32 * 14.0).sin(); - let wave_quick = (anim_time as f32 * 20.0).sin(); - let wave_quick_cos = (anim_time as f32 * 20.0).cos(); - let wave_cos = (anim_time as f32 * 14.0).cos(); + let slow = (anim_time as f32 * 14.0).sin(); + let fast = (anim_time as f32 * 20.0).sin(); + let fast_alt = (anim_time as f32 * 20.0 + PI / 2.0).sin(); + let slow_alt = (anim_time as f32 * 14.0 + PI / 2.0).sin(); next.head.offset = - Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + wave * 1.5) / 11.0; + Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + slow * 1.5) / 11.0; next.head.ori = - Quaternion::rotation_x(0.2 + wave * 0.05) * Quaternion::rotation_y(wave_cos * 0.03); + Quaternion::rotation_x(0.2 + slow * 0.05) * Quaternion::rotation_y(slow_alt * 0.03); next.head.scale = Vec3::one() / 10.5; next.chest.offset = Vec3::new( 0.0, skeleton_attr.chest.0, - skeleton_attr.chest.1 + wave_cos * 1.2, + skeleton_attr.chest.1 + slow_alt * 1.2, ) / 11.0; - next.chest.ori = Quaternion::rotation_x(wave * 0.1); + next.chest.ori = Quaternion::rotation_x(slow * 0.1); next.chest.scale = Vec3::one() / 11.0; next.leg_lf.offset = Vec3::new( -skeleton_attr.feet_f.0, - skeleton_attr.feet_f.1 + wave_quick * 0.8, - skeleton_attr.feet_f.2 + wave_quick_cos * 1.5, + skeleton_attr.feet_f.1 + fast * 0.8, + skeleton_attr.feet_f.2 + fast_alt * 1.5, ) / 11.0; - next.leg_lf.ori = Quaternion::rotation_x(wave_quick * 0.3); + next.leg_lf.ori = Quaternion::rotation_x(fast * 0.3); next.leg_lf.scale = Vec3::one() / 11.0; next.leg_rf.offset = Vec3::new( skeleton_attr.feet_f.0, - skeleton_attr.feet_f.1 - wave_quick_cos * 0.8, - skeleton_attr.feet_f.2 + wave_quick * 1.5, + skeleton_attr.feet_f.1 + fast_alt * -0.8, + skeleton_attr.feet_f.2 + fast * 1.5, ) / 11.0; - next.leg_rf.ori = Quaternion::rotation_x(wave_quick_cos * -0.3); + next.leg_rf.ori = Quaternion::rotation_x(fast_alt * -0.3); next.leg_rf.scale = Vec3::one() / 11.0; next.leg_lb.offset = Vec3::new( -skeleton_attr.feet_b.0, - skeleton_attr.feet_b.1 - wave_quick_cos * 0.8, - skeleton_attr.feet_b.2 + wave_quick * 1.5, + skeleton_attr.feet_b.1 + fast_alt * -0.8, + skeleton_attr.feet_b.2 + fast * 1.5, ) / 11.0; - next.leg_lb.ori = Quaternion::rotation_x(wave_quick_cos * -0.3); + next.leg_lb.ori = Quaternion::rotation_x(fast_alt * -0.3); next.leg_lb.scale = Vec3::one() / 11.0; next.leg_rb.offset = Vec3::new( skeleton_attr.feet_b.0, - skeleton_attr.feet_b.1 + wave_quick * 0.8, - skeleton_attr.feet_b.2 + wave_quick_cos * 1.5, + skeleton_attr.feet_b.1 + fast * 0.8, + skeleton_attr.feet_b.2 + fast_alt * 1.5, ) / 11.0; - next.leg_rb.ori = Quaternion::rotation_x(wave_quick * 0.3); + next.leg_rb.ori = Quaternion::rotation_x(fast * 0.3); next.leg_rb.scale = Vec3::one() / 11.0; next From cf2c3ae8560f882f0a26886276a09610c887ef2a Mon Sep 17 00:00:00 2001 From: Caleb Cochran Date: Tue, 25 Feb 2020 20:48:09 -0600 Subject: [PATCH 02/22] Fixing #504 - Enemies stop attacking after combat --- common/src/sys/agent.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index f2268d439c..3d126f4ecd 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -124,7 +124,7 @@ impl<'a> System<'a> for Sys { if thread_rng().gen::() < 0.1 { choose_target = true; } - }, + } Activity::Follow(target, chaser) => { if let (Some(tgt_pos), _tgt_stats) = (positions.get(*target), stats.get(*target)) @@ -146,14 +146,14 @@ impl<'a> System<'a> for Sys { } else { do_idle = true; } - }, + } Activity::Attack { target, chaser, been_close, .. } => { - if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = ( + if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = ( positions.get(*target), stats.get(*target), alignments @@ -164,7 +164,8 @@ impl<'a> System<'a> for Sys { // Don't attack entities we are passive towards // TODO: This is here, it's a bit of a hack if let Some(alignment) = alignment { - if (*alignment).passive_towards(tgt_alignment) { + if (*alignment).passive_towards(tgt_alignment) || tgt_stats.is_dead + { do_idle = true; break 'activity; } @@ -207,7 +208,7 @@ impl<'a> System<'a> for Sys { } else { do_idle = true; } - }, + } } } From 708ec6f8c06d6c3c43c12caa494b88d6a5c87a54 Mon Sep 17 00:00:00 2001 From: Caleb Cochran Date: Tue, 25 Feb 2020 21:59:08 -0600 Subject: [PATCH 03/22] Replaced commas (fmt issue) --- common/src/sys/agent.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 3d126f4ecd..07788bf1a2 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -124,7 +124,7 @@ impl<'a> System<'a> for Sys { if thread_rng().gen::() < 0.1 { choose_target = true; } - } + }, Activity::Follow(target, chaser) => { if let (Some(tgt_pos), _tgt_stats) = (positions.get(*target), stats.get(*target)) @@ -146,7 +146,7 @@ impl<'a> System<'a> for Sys { } else { do_idle = true; } - } + }, Activity::Attack { target, chaser, From 4b3ca906a01430d46748e4685518652ea6ca21ac Mon Sep 17 00:00:00 2001 From: Caleb Cochran Date: Tue, 25 Feb 2020 22:01:51 -0600 Subject: [PATCH 04/22] Added missed comma --- common/src/sys/agent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 07788bf1a2..9d8415ea28 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -208,7 +208,7 @@ impl<'a> System<'a> for Sys { } else { do_idle = true; } - } + }, } } From 5795e5354985d5fce66638e413983d5f832f8e9b Mon Sep 17 00:00:00 2001 From: Caleb Cochran Date: Tue, 25 Feb 2020 22:41:58 -0600 Subject: [PATCH 05/22] Added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 055492a259..71ac390130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added gamma setting - Added new orc hairstyles - Added sfx for wielding/unwielding weapons +- Fixed NPCs attacking the player forever after killing them ### Changed From 8ecdb530763a4091054ff29cf355878cc7432b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Wed, 26 Feb 2020 00:10:00 +0100 Subject: [PATCH 06/22] auto-generated docker image for server-cli based on kaniko like i researched for torvus and correct release branch detection: - https://gitlab.com/veloren/torvus/-/commit/ade4d375756ffa1eb1ac32f25aec3c6738bed6cc --- .gitlab-ci.yml | 49 ++++++++++++++++++++++++++++------- server-cli/Dockerfile | 8 ++++++ server-cli/docker-compose.yml | 17 ++++++++++++ server-cli/docker-run.sh | 3 +++ 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 server-cli/Dockerfile create mode 100644 server-cli/docker-compose.yml create mode 100755 server-cli/docker-run.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 813eabf4c0..b90be89601 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,8 @@ variables: stages: - optional-builds - check-compile - - post + - build-post + - publish before_script: - source $HOME/.cargo/env @@ -90,10 +91,10 @@ security: # -- -# -- post build +# -- build-post unittests: - stage: post + stage: build-post when: delayed start_in: 5 seconds tags: @@ -103,7 +104,7 @@ unittests: - cargo test || cargo test || cargo test || cargo test coverage: - stage: post + stage: build-post when: delayed start_in: 5 seconds tags: @@ -113,7 +114,7 @@ coverage: - cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v benchmarks: - stage: post + stage: build-post when: delayed start_in: 5 seconds tags: @@ -125,7 +126,7 @@ benchmarks: localization-status: variables: GIT_DEPTH: 0 - stage: post + stage: build-post when: delayed start_in: 5 seconds allow_failure: true @@ -135,14 +136,15 @@ localization-status: - cargo test -q test_all_localizations -- --nocapture --ignored linux: - stage: post + stage: build-post when: delayed start_in: 5 seconds only: refs: - /^r[0-9]+\.[0-9]+\.[0-9]+/ - - /^v[0-9]+\.[0-9]+\.[0-9]+/ + - /^v[0-9]+\.[0-9]+/ - /^master$/ + - /^docker-server$/ tags: - veloren-docker script: @@ -160,13 +162,13 @@ linux: expire_in: 1 week windows: - stage: post + stage: build-post when: delayed start_in: 5 seconds only: refs: - /^r[0-9]+\.[0-9]+\.[0-9]+/ - - /^v[0-9]+\.[0-9]+\.[0-9]+/ + - /^v[0-9]+\.[0-9]+/ - /^master$/ tags: - veloren-docker @@ -182,3 +184,30 @@ windows: - LICENSE expire_in: 1 week # -- + +# -- publish + +docker: + stage: publish + when: delayed + start_in: 5 seconds + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + dependencies: + - linux + before_script: + - ls "$CI_PROJECT_DIR/server-cli/" + only: + refs: + - /^r[0-9]+\.[0-9]+\.[0-9]+/ + - /^v[0-9]+\.[0-9]+/ + - /^master$/ + - /^docker-server$/ + tags: + - veloren-docker + script: + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/server-cli/Dockerfile --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}-server" + +# -- diff --git a/server-cli/Dockerfile b/server-cli/Dockerfile new file mode 100644 index 0000000000..eacecc7846 --- /dev/null +++ b/server-cli/Dockerfile @@ -0,0 +1,8 @@ +FROM debian:stable-slim + +ARG PROJECTNAME=server-cli + +COPY ./server-cli/docker-run.sh /opt/docker-run.sh +COPY ./veloren-server-cli /opt/veloren-server-cli +COPY ./assets/common /opt/assets/common +COPY ./assets/world /opt/assets/world diff --git a/server-cli/docker-compose.yml b/server-cli/docker-compose.yml new file mode 100644 index 0000000000..ba9badf7de --- /dev/null +++ b/server-cli/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.7" + +services: + game-server: + image: registry.gitlab.com/veloren/veloren:master-server + ports: + - "14004:14004" + - "14005:14005" + deploy: + replicas: 1 + update_config: + parallelism: 2 + delay: 10s + order: stop-first + failure_action: rollback + restart_policy: + condition: on-failure diff --git a/server-cli/docker-run.sh b/server-cli/docker-run.sh new file mode 100755 index 0000000000..e965090fdb --- /dev/null +++ b/server-cli/docker-run.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cd /opt +RUST_LOG=info,common=debug,common::net=info RUST_BACKTRACE=1 /opt/veloren-server-cli From 6ba112f31804c2eccc5296018461f3d640423223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Wed, 26 Feb 2020 17:10:47 +0100 Subject: [PATCH 07/22] automated macos builds --- .gitlab-ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b90be89601..353ec24fb0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -183,6 +183,30 @@ windows: - assets/ - LICENSE expire_in: 1 week + +macos: + stage: build-post + when: delayed + start_in: 5 seconds + only: + refs: + - /^r[0-9]+\.[0-9]+\.[0-9]+/ + - /^v[0-9]+\.[0-9]+/ + - /^master$/ + - /^docker-server$/ + tags: + - veloren-docker + script: + - PATH="/dockercache/osxcross/target/bin:$PATH" COREAUDIO_SDK_PATH=/dockercache/osxcross/target/SDK/MacOSX10.13.sdk CC=o64-clang CXX=o64-clang++ cargo build --target x86_64-apple-darwin --release + - cp -r target/x86_64-apple-darwin/release/veloren-server-cli $CI_PROJECT_DIR + - cp -r target/x86_64-apple-darwin/release/veloren-voxygen $CI_PROJECT_DIR + artifacts: + paths: + - veloren-server-cli + - veloren-voxygen + - assets/ + - LICENSE + expire_in: 1 week # -- # -- publish From 66e7c7f338753fe66315514aee48f890ad4de79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Thu, 27 Feb 2020 13:43:43 +0100 Subject: [PATCH 08/22] workaround for tests and tarpaulin --- .gitlab-ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 353ec24fb0..0a93f8de07 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,7 +101,7 @@ unittests: - veloren-docker script: - echo "Workaround, cargo tests fails due some rust files are already deleted, so we just stack cargo test. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail" - - cargo test || cargo test || cargo test || cargo test + - cargo test || ( sleep 10 && cargo test ) || ( sleep 10 && cargo test ) || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test coverage: stage: build-post @@ -111,7 +111,7 @@ coverage: - veloren-docker script: - echo "Workaround, tarpaulin fails due some rust files are already deleted, so we just stack tarpaulin. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail" - - cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v + - cargo tarpaulin -v || ( sleep 10 && cargo tarpaulin -v ) || ( sleep 10 && cargo tarpaulin -v ) || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v benchmarks: stage: build-post @@ -144,7 +144,6 @@ linux: - /^r[0-9]+\.[0-9]+\.[0-9]+/ - /^v[0-9]+\.[0-9]+/ - /^master$/ - - /^docker-server$/ tags: - veloren-docker script: @@ -193,7 +192,6 @@ macos: - /^r[0-9]+\.[0-9]+\.[0-9]+/ - /^v[0-9]+\.[0-9]+/ - /^master$/ - - /^docker-server$/ tags: - veloren-docker script: @@ -227,7 +225,6 @@ docker: - /^r[0-9]+\.[0-9]+\.[0-9]+/ - /^v[0-9]+\.[0-9]+/ - /^master$/ - - /^docker-server$/ tags: - veloren-docker script: From 272d5eadcbeed71c4b1691136504108d34f9fbb1 Mon Sep 17 00:00:00 2001 From: Artem Polishchuk Date: Sun, 17 Nov 2019 21:04:37 +0200 Subject: [PATCH 09/22] Add desktop file and AppData manifest Fix screenshot duplicate in appdata Move .png icon to git lfs --- .../voxygen/net.veloren.veloren.appdata.xml | 61 ++++++++++++++++++ assets/voxygen/net.veloren.veloren.desktop | 9 +++ assets/voxygen/net.veloren.veloren.png | Bin 0 -> 33292 bytes 3 files changed, 70 insertions(+) create mode 100644 assets/voxygen/net.veloren.veloren.appdata.xml create mode 100644 assets/voxygen/net.veloren.veloren.desktop create mode 100644 assets/voxygen/net.veloren.veloren.png diff --git a/assets/voxygen/net.veloren.veloren.appdata.xml b/assets/voxygen/net.veloren.veloren.appdata.xml new file mode 100644 index 0000000000..1192abe8fc --- /dev/null +++ b/assets/voxygen/net.veloren.veloren.appdata.xml @@ -0,0 +1,61 @@ + + + + net.veloren.veloren.desktop + CC0-1.0 + GPL-3.0-or-later + + intense + mild + mild + mild + + Veloren + + Veloren is a multiplayer voxel RPG written in Rust. It is inspired + by games such as Cube World, Legend of Zelda: Breath of the Wild, + Dwarf Fortress and Minecraft. + + +

+ Welcome To Veloren! +

+ Veloren is a multiplayer voxel RPG written in Rust. Veloren takes + inspiration from games such as Cube World, Minecraft and Dwarf + Fortress. The game is currently under heavy development, but is + playable. +

+ Development +

+ Currently the communication of contributors happens mainly on our + official Discord server (https://discord.gg/kjwJwjK). You can join + it to keep up with the development, talk to us or contribute + something yourself. Anyone who shows genuine effort to help is + welcome in our team. You don't have to know how to program to + contribute! +

+
+ + + https://media.discordapp.net/attachments/634860358623821835/643034796548947968/screenshot_1573381825305.png + + + https://media.discordapp.net/attachments/597826574095613962/643102462781423616/screenshot_1573397958545.png + + + https://cdn.discordapp.com/attachments/634860358623821835/646518917577310219/screenshot_1574211401431.png + + + + sandbox + world + multiplayer + + https://veloren.net + https://gitlab.com/veloren/veloren/issues + https://gitlab.com/veloren/veloren#faq + https://book.veloren.net/ + + veloren-voxygen + +
diff --git a/assets/voxygen/net.veloren.veloren.desktop b/assets/voxygen/net.veloren.veloren.desktop new file mode 100644 index 0000000000..5e4f2cd02e --- /dev/null +++ b/assets/voxygen/net.veloren.veloren.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=Veloren +Comment=Veloren is a multiplayer voxel RPG written in Rust +Exec=veloren-voxygen +Categories=Game;Simulation; +Keywords=veloren;sandbox;world;blocks;nodes;multiplayer;roleplaying; +Icon=net.veloren.veloren.png +Terminal=false diff --git a/assets/voxygen/net.veloren.veloren.png b/assets/voxygen/net.veloren.veloren.png new file mode 100644 index 0000000000000000000000000000000000000000..356c33aabc9f3c6ab7b12709110dd980feb71923 GIT binary patch literal 33292 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 Date: Sun, 1 Mar 2020 19:45:05 +0000 Subject: [PATCH 10/22] Revert "Do that better, and add a TODO." This reverts commit 9b0f11bd8922491bf2c2a9b4422d59982b227564. It wasn't better. --- voxygen/src/audio/sfx/event_mapper/movement/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 0b0294ecec..77c2067177 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -157,10 +157,11 @@ impl MovementEventMapper { { if let Some(wield_event) = match ( previous_event.weapon_drawn, + current_event.action.is_roll(), Self::has_weapon_drawn(current_event.action), ) { - (false, true) => Some(SfxEvent::Wield(kind)), - (true, false) => Some(SfxEvent::Unwield(kind)), + (false, false, true) => Some(SfxEvent::Wield(kind)), + (true, false, false) => Some(SfxEvent::Unwield(kind)), _ => None, } { return wield_event; From c7b4b94b156357e26f865281ffbebea7a78c7d8d Mon Sep 17 00:00:00 2001 From: S Handley Date: Wed, 4 Mar 2020 10:09:48 +0000 Subject: [PATCH 11/22] Piggyback on the InventoryUpdate events and attach some additional event info so that we can detect why the inventory update was triggered, and emit an associated sfx event that matches it. --- CHANGELOG.md | 1 + assets/voxygen/audio/sfx.ron | 54 ++++++++++++++++++ .../voxygen/audio/sfx/inventory/add_item.wav | Bin 0 -> 71196 bytes .../audio/sfx/inventory/consumable/apple.wav | Bin 0 -> 70152 bytes .../audio/sfx/inventory/consumable/food.wav | Bin 0 -> 31208 bytes .../audio/sfx/inventory/consumable/liquid.wav | Bin 0 -> 21548 bytes client/src/lib.rs | 12 +++- common/src/comp/inventory/item.rs | 2 +- common/src/comp/inventory/mod.rs | 36 ++++++++++-- common/src/comp/mod.rs | 2 +- common/src/event.rs | 3 +- common/src/msg/server.rs | 2 +- server/src/cmd.rs | 11 +++- server/src/events/interaction.rs | 5 +- server/src/events/inventory_manip.rs | 22 +++++-- server/src/lib.rs | 10 +++- server/src/sys/entity_sync.rs | 7 ++- voxygen/src/audio/sfx/event_mapper/mod.rs | 4 +- .../audio/sfx/event_mapper/movement/mod.rs | 2 +- 19 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 assets/voxygen/audio/sfx/inventory/add_item.wav create mode 100644 assets/voxygen/audio/sfx/inventory/consumable/apple.wav create mode 100644 assets/voxygen/audio/sfx/inventory/consumable/food.wav create mode 100644 assets/voxygen/audio/sfx/inventory/consumable/liquid.wav diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ac390130..0e3ca83fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new orc hairstyles - Added sfx for wielding/unwielding weapons - Fixed NPCs attacking the player forever after killing them +- Added sfx for collecting, dropping and using inventory items ### Changed diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 38349f287e..2a8d79e5e2 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -35,5 +35,59 @@ ], threshold: 0.5, ), + Inventory(Collected): ( + files: [ + "voxygen.audio.sfx.inventory.add_item", + ], + threshold: 0.5, + ), + Inventory(Swapped): ( + files: [ + "voxygen.audio.sfx.inventory.add_item", + ], + threshold: 0.5, + ), + Inventory(Given): ( + files: [ + "voxygen.audio.sfx.inventory.add_item", + ], + threshold: 0.5, + ), + Inventory(Dropped): ( + files: [ + "voxygen.audio.sfx.footsteps.stepgrass_4", + ], + threshold: 0.5, + ), + Inventory(Consumed(Potion)): ( + files: [ + "voxygen.audio.sfx.inventory.consumable.liquid", + ], + threshold: 0.3, + ), + Inventory(Consumed(PotionMinor)): ( + files: [ + "voxygen.audio.sfx.inventory.consumable.liquid", + ], + threshold: 0.3, + ), + Inventory(Consumed(Apple)): ( + files: [ + "voxygen.audio.sfx.inventory.consumable.apple", + ], + threshold: 0.3, + ), + Inventory(Consumed(Mushroom)): ( + files: [ + "voxygen.audio.sfx.inventory.consumable.food", + ], + threshold: 0.3, + ), + Inventory(Consumed(Cheese)): ( + files: [ + "voxygen.audio.sfx.inventory.consumable.food", + ], + threshold: 0.3, + ) } ) \ No newline at end of file diff --git a/assets/voxygen/audio/sfx/inventory/add_item.wav b/assets/voxygen/audio/sfx/inventory/add_item.wav new file mode 100644 index 0000000000000000000000000000000000000000..3c6f68dd078ede9659b072c1dfd27eabd49837cd GIT binary patch literal 71196 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 literal 0 HcmV?d00001 diff --git a/assets/voxygen/audio/sfx/inventory/consumable/apple.wav b/assets/voxygen/audio/sfx/inventory/consumable/apple.wav new file mode 100644 index 0000000000000000000000000000000000000000..4478493a55405ac31a9c7ac7f591a48a5395654c GIT binary patch literal 70152 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 r}j0v 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 Vn+kRDdH8Xsq}Ox&?h3Z~{{U*gIcWd@ literal 0 HcmV?d00001 diff --git a/assets/voxygen/audio/sfx/inventory/consumable/food.wav b/assets/voxygen/audio/sfx/inventory/consumable/food.wav new file mode 100644 index 0000000000000000000000000000000000000000..13f5fdcf1b756c860b7d760ea3dcd38af48da29a GIT binary patch literal 31208 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 literal 0 HcmV?d00001 diff --git a/assets/voxygen/audio/sfx/inventory/consumable/liquid.wav b/assets/voxygen/audio/sfx/inventory/consumable/liquid.wav new file mode 100644 index 0000000000000000000000000000000000000000..6d048a79c5b87a0a7428d4cda156ee6917dcfab3 GIT binary patch literal 21548 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 z { - self.state.write_component(self.entity, inventory) + ServerMsg::InventoryUpdate(inventory, event) => { + self.state.write_component(self.entity, inventory); + + self.state + .ecs() + .read_resource::>() + .emitter() + .emit(SfxEventItem::at_player_position(SfxEvent::Inventory(event))); }, ServerMsg::TerrainChunkUpdate { key, chunk } => { if let Ok(chunk) = chunk { diff --git a/common/src/comp/inventory/item.rs b/common/src/comp/inventory/item.rs index da958ee9b9..dd25ef9e4f 100644 --- a/common/src/comp/inventory/item.rs +++ b/common/src/comp/inventory/item.rs @@ -90,7 +90,7 @@ pub enum Armor { Necklace, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Consumable { Apple, Cheese, diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index f6833ff923..f192208b5c 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,10 +1,11 @@ pub mod item; // Reexports -pub use item::{Debug, Item, ItemKind, Tool}; +pub use item::{Consumable, Debug, Item, ItemKind, Tool}; use crate::assets; -use specs::{Component, HashMapStorage, NullStorage}; +use specs::{Component, FlaggedStorage, HashMapStorage}; +use specs_idvs::IDVStorage; use std::ops::Not; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -136,12 +137,37 @@ impl Component for Inventory { type Storage = HashMapStorage; } -// ForceUpdate +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub enum InventoryUpdateEvent { + Init, + Used, + Consumed(Consumable), + Gave, + Given, + Swapped, + Dropped, + Collected, + Possession, + Debug, +} + +impl Default for InventoryUpdateEvent { + fn default() -> Self { Self::Init } +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] -pub struct InventoryUpdate; +pub struct InventoryUpdate { + event: InventoryUpdateEvent, +} + +impl InventoryUpdate { + pub fn new(event: InventoryUpdateEvent) -> Self { Self { event } } + + pub fn event(&self) -> InventoryUpdateEvent { self.event } +} impl Component for InventoryUpdate { - type Storage = NullStorage; + type Storage = FlaggedStorage>; } #[cfg(test)] mod test; diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 24b1be9efb..2871761a7a 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -28,7 +28,7 @@ pub use controller::{ }; pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; -pub use inventory::{item, Inventory, InventoryUpdate, Item, ItemKind}; +pub use inventory::{item, Inventory, InventoryUpdate, InventoryUpdateEvent, Item, ItemKind}; pub use last::Last; pub use location::{Waypoint, WaypointArea}; pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; diff --git a/common/src/event.rs b/common/src/event.rs index 6ac5e49531..0ba59b7274 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -1,5 +1,5 @@ use crate::{comp, sync::Uid}; -use comp::item::Tool; +use comp::{item::Tool, InventoryUpdateEvent}; use parking_lot::Mutex; use serde::Deserialize; use specs::Entity as EcsEntity; @@ -42,6 +42,7 @@ pub enum SfxEvent { LevelUp, Wield(Tool), Unwield(Tool), + Inventory(InventoryUpdateEvent), } pub enum LocalEvent { diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index a9244b15f6..0a7b84ae33 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -71,7 +71,7 @@ pub enum ServerMsg { entity: u64, character_state: comp::CharacterState, }, - InventoryUpdate(comp::Inventory), + InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent), TerrainChunkUpdate { key: Vec2, chunk: Result, ()>, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 1f46109481..b4a2e3e5dd 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -262,6 +262,7 @@ lazy_static! { ), ]; } + fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &ChatCommand) { if let Ok(item) = assets::load_cloned(&args) { server @@ -274,7 +275,10 @@ fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &C .state .ecs() .write_storage::() - .insert(entity, comp::InventoryUpdate); + .insert( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given), + ); } else { server.notify_client(entity, ServerMsg::private(String::from("Invalid item!"))); } @@ -1113,7 +1117,10 @@ fn handle_debug(server: &mut Server, entity: EcsEntity, _args: String, _action: .state .ecs() .write_storage::() - .insert(entity, comp::InventoryUpdate); + .insert( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug), + ); } else { server.notify_client( entity, diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 96be66e52b..7d7e3fd124 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -106,7 +106,10 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { } } ecs.write_storage::() - .insert(possesse, comp::InventoryUpdate) + .insert( + possesse, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Possession), + ) .err() .map(|e| { error!( diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 4485a62fb7..09998ca6aa 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -48,7 +48,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv } } - state.write_component(entity, comp::InventoryUpdate); + state.write_component( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected), + ); }, comp::InventoryManip::Collect(pos) => { @@ -76,6 +79,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .get_mut(entity) .and_then(|inv| inv.remove(slot)); + let mut event = comp::InventoryUpdateEvent::Used; + if let Some(item) = item_opt { match item.kind { comp::ItemKind::Tool { .. } => { @@ -94,7 +99,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv stats.equipment.main = Some(item); } }, - comp::ItemKind::Consumable { effect, .. } => { + comp::ItemKind::Consumable { kind, effect } => { + event = comp::InventoryUpdateEvent::Consumed(kind); state.apply_effect(entity, effect); }, comp::ItemKind::Utility { kind } => match kind { @@ -168,7 +174,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv } } - state.write_component(entity, comp::InventoryUpdate); + state.write_component(entity, comp::InventoryUpdate::new(event)); }, comp::InventoryManip::Swap(a, b) => { @@ -177,7 +183,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .write_storage::() .get_mut(entity) .map(|inv| inv.swap_slots(a, b)); - state.write_component(entity, comp::InventoryUpdate); + state.write_component( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Swapped), + ); }, comp::InventoryManip::Drop(slot) => { @@ -201,7 +210,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv item, )); } - state.write_component(entity, comp::InventoryUpdate); + state.write_component( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Dropped), + ); }, } diff --git a/server/src/lib.rs b/server/src/lib.rs index b39fefea50..4d11efd110 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -282,7 +282,10 @@ impl Server { state.write_component(entity, comp::CharacterState::default()); state.write_component(entity, comp::Alignment::Owned(entity)); state.write_component(entity, comp::Inventory::default()); - state.write_component(entity, comp::InventoryUpdate); + state.write_component( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()), + ); // Make sure physics are accepted. state.write_component(entity, comp::ForceUpdate); @@ -606,7 +609,10 @@ impl StateExt for State { .map(|inv| inv.push(item).is_none()) .unwrap_or(false); if success { - self.write_component(entity, comp::InventoryUpdate); + self.write_component( + entity, + comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected), + ); } success } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 1d28b77117..62bc7521a7 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -328,8 +328,11 @@ impl<'a> System<'a> for Sys { // TODO: Sync clients that don't have a position? // Sync inventories - for (client, inventory, _) in (&mut clients, &inventories, &inventory_updates).join() { - client.notify(ServerMsg::InventoryUpdate(inventory.clone())); + for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() { + client.notify(ServerMsg::InventoryUpdate( + inventory.clone(), + update.event(), + )); } // Remove all force flags. diff --git a/voxygen/src/audio/sfx/event_mapper/mod.rs b/voxygen/src/audio/sfx/event_mapper/mod.rs index e195d1cba2..94905cb802 100644 --- a/voxygen/src/audio/sfx/event_mapper/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/mod.rs @@ -1,5 +1,5 @@ -pub mod movement; -pub mod progression; +mod movement; +mod progression; use movement::MovementEventMapper; use progression::ProgressionEventMapper; diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 77c2067177..617092e8d6 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -172,7 +172,7 @@ impl MovementEventMapper { match ( current_event.movement, current_event.action, - previous_event.event, + previous_event.event.clone(), ) { (_, ActionState::Roll { .. }, _) => SfxEvent::Roll, (MovementState::Climb, ..) => SfxEvent::Climb, From 5b370a4f8671f464c2426a1353cf02c3d2fa76b5 Mon Sep 17 00:00:00 2001 From: Shane Handley Date: Thu, 5 Mar 2020 16:11:45 +0900 Subject: [PATCH 12/22] Deselect the inventory slot after dropping an item. --- voxygen/src/hud/bag.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 22d763ed42..70e27498de 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -258,6 +258,7 @@ impl<'a> Widget for Bag<'a> { if let Some(to_drop) = state.selected_slot { if ui.widget_input(ui.window).clicks().left().next().is_some() { event = Some(Event::HudEvent(HudEvent::DropInventorySlot(to_drop))); + state.update(|s| s.selected_slot = None); } } From f8d18e4913061eb71687e35ebac94f567c384e6d Mon Sep 17 00:00:00 2001 From: Justin Shipsey Date: Thu, 5 Mar 2020 14:02:11 +0000 Subject: [PATCH 13/22] weapon control bone --- CHANGELOG.md | 2 + assets/voxygen/element/frames/banner_top.png | Bin 728 -> 4881 bytes .../voxygen/element/misc_bg/textbox_bot.png | Bin 1017 -> 6377 bytes .../voxygen/element/misc_bg/textbox_mid.png | Bin 839 -> 6103 bytes .../voxygen/element/misc_bg/textbox_top.png | Bin 1020 -> 6358 bytes voxygen/src/anim/biped_large/mod.rs | 4 +- voxygen/src/anim/bird_medium/mod.rs | 4 +- voxygen/src/anim/bird_small/mod.rs | 4 +- voxygen/src/anim/character/attack.rs | 86 +++++++++------- voxygen/src/anim/character/block.rs | 24 +++-- voxygen/src/anim/character/blockidle.rs | 24 +++-- voxygen/src/anim/character/charge.rs | 5 + voxygen/src/anim/character/cidle.rs | 36 +++---- voxygen/src/anim/character/climb.rs | 41 ++++++-- voxygen/src/anim/character/crun.rs | 36 ++++--- voxygen/src/anim/character/gliding.rs | 35 +++++-- voxygen/src/anim/character/idle.rs | 39 +++++-- voxygen/src/anim/character/jump.rs | 35 +++++-- voxygen/src/anim/character/mod.rs | 31 ++++-- voxygen/src/anim/character/roll.rs | 40 ++++++-- voxygen/src/anim/character/run.rs | 95 ++++++++++++------ voxygen/src/anim/character/sit.rs | 36 +++++-- voxygen/src/anim/character/stand.rs | 48 ++++++--- voxygen/src/anim/character/swim.rs | 37 +++++-- voxygen/src/anim/character/wield.rs | 28 ++++-- voxygen/src/anim/critter/mod.rs | 4 +- voxygen/src/anim/dragon/mod.rs | 4 +- voxygen/src/anim/fish_medium/mod.rs | 4 +- voxygen/src/anim/fish_small/mod.rs | 4 +- voxygen/src/anim/fixture/mod.rs | 4 +- voxygen/src/anim/mod.rs | 2 +- voxygen/src/anim/object/mod.rs | 4 +- voxygen/src/anim/quadruped_medium/mod.rs | 4 +- voxygen/src/anim/quadruped_small/mod.rs | 4 +- voxygen/src/scene/figure/cache.rs | 26 ++--- 35 files changed, 505 insertions(+), 245 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e3ca83fa6..227c50b450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added sfx for wielding/unwielding weapons - Fixed NPCs attacking the player forever after killing them - Added sfx for collecting, dropping and using inventory items +- New attack animation +- weapon control system ### Changed diff --git a/assets/voxygen/element/frames/banner_top.png b/assets/voxygen/element/frames/banner_top.png index d3561d3a9cda48d61449495b3d8d998a51a2f28e..e75a275660511ca78201e543a94edcba06e9586c 100644 GIT binary patch delta 4875 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 x06<2WG#Q*>R07*qoM6N<$0fNgoSMC4+ delta 687 zcmV;g0#N;tCfEg#Ba?v-7JndPNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe z02gnPU&TfM00Le~L_t(|+U%W6a)U4sL?Pu%!1cV$Dkt&E%UsXCMpjg%C{)sDdL)qF zTjgWMAPt&UOUCmwJ}>9-t0)fm+J6e_hqY5a4apbz3IHnR?T7p0Ie)|j{r-F(R*?JS zxftWTEphg*FMYq-<5b7@j&E&$)p;CT*J{ZtVLq*c+;=Dx0MK$9^}@CalB;yq{_whFcvR4dVWunf@vx7nbfH;Mc@*plar+Zh9p=1COAn;hgRv?J@9TRj&!{|U6?Yp)P$&SP_fVoo zRrquw{Uc1588fL@`ruKZW6oPSXsK-3(#_s-o~&&zw= ztk%W$XR5tWC;$+rP*g7P*;je23Ze8o)T8JpLO3B`%nO&y@wM08IN9-$h&G4{XCK0bKtYh!FxY1CZ{~Sc{GZ$ zOpO;P6adhBD5K%60JXuU3p-^2AXK4j->dOLsQ^HYGMRYlJpibg5B~O|StJ0U5WYDO0Alz9009600{~#_ V(7>zx6@mZ&002ovPDHLkV1g@fMCAYg diff --git a/assets/voxygen/element/misc_bg/textbox_bot.png b/assets/voxygen/element/misc_bg/textbox_bot.png index c28435b70353ea15fae94590b4c79672d1d5c893..330ab52d801bfa21f6f209c45ce6a9506600601e 100644 GIT binary patch literal 6377 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 delta 996 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 diff --git a/assets/voxygen/element/misc_bg/textbox_top.png b/assets/voxygen/element/misc_bg/textbox_top.png index 8431fc58ced781bd990d79fc560e7a8b56e7172b..8666ed9d1fbfee2f06a3fb31c290cded1ba0c5a5 100644 GIT binary patch literal 6358 zcmV;{7%At8P)_%000ysdQ@0+Qek%> zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3*tk{r2_h5zFea|CRT7{u7j$sfuQ$~1jl%r>bywH-KJ9y>?=ODbFzLyWAHLVM?~PLY zydJ*)cK!Rlk^i!t@84A4Eq-47_kZU~F}CqyiY}fMlIOpAV@#jFgTLwh*SN^PS3a+& z)c;&N_w&cU6)&eh*Updc73udU=e}xG4Ijo0G5QLJyH@-;l!& zBiwM__Z1dP%<;s^_ZU}9@3od{>~T@}kg~qQjWzYO0VMrcbS?36{JEC!p4;E^R%kqV z2d<2Pn+4wS4`1$2FaBS?e%|O_L6-uz?}}c7qX5V<)H(f^t7u5L-P((7~LI`i5258OlD-y5(Xo^329Bs$EvUloO;^nXI!v$ z)6Lgzx%IZ&@A%oZZ&!csTKHS%{&m;Fw`=NHe%IAsUE}3if7~L7PE0+cV=)IhUey5r z9n~}6Le5d0Q_p;l424Cy7EI0AK^>!m`GjaU{OsMY&i%{2Im`c1-{L>(oKx%mKRV~s zx^L(Hv2TCq+8$3)KGUcvR84)l5Zm>8!y&6vvfg1YYa_H(4zfu}yo$2#P-T}tmw}~S zZO(AmeadCVY~i>Gr$ISvF)0bEFNMS-dC?dpkRy%=Cb_X~o|HGoCeL+wLNq! z_Qlg{rVaNt7I#l?Wtz6r;)3sYXJyo4Qt80ZPs(1>Mi(7DOS*7`UGwy3f}&&l!0ngv z+stjGIbn8h7Z|p**xDM)-$4hgENgiqN5;zU2?abMEh(+KJKx&frL)jz8;5cai}R{B zm-ZfMUG3+@U2Sts4!yV@^Nwwd(Wpe87n(ZzQsJuQuMS{%7Ob~H7??poeBDe-9*2MB zd8l98Ap7j1k;w73c4)d!ZH`$&K>5RejsbL5`?S=tXl-E$~)Rc}n?&nR$r zSY-e)yijG#0rEZtUQ3Auj|6?Uu+y|Sb1ranVQT44%@?u!!1&1~h>x zSek_y8sc!6a?S$?i?I$l z6#yoAvQ>!?6Le*N*!)3igq9jIV6Q86Zn<$?G&D*^DnixZ4Mq+UfQb@af)#En+0}+n ztX*dIlbJ$L7*p;OOV4c5Y;(gfTMI~!F}e4QUSRrWBk!<~H>wbpvn}?+mQyz>gZjRxd4L1qZ^H^+HB!Z9lsX%bl(Q@-Z>|fI`?W>1fTYd7Z!mEN4-*1ND@xA0gwfjBPRA}IM%FtC&f=A)j4!VI2wZBYXH3C%e6 z7(?Ng$PrWq25$e0DR1%{H$qj1I_nDMDLixHXi)joNgynO7541ku^Rwdfvx)>A!M2&8(_*= z--1M#eqat>P`)OIgo=c?VSt`l;)H%N$2v?oRkJaql#~z<3?d7br%Qr&!b5s=u>|0T zA2?HG!lv%KkhX?fNOT4GFu-1{d2tAg_Si$FH&dvRp?J8^(a7+OmN#WcC=|;F(ZnNj z1vNu%Gz~Vahw{a<=wcRb(bAKcT{^+V4r+?r8nU2Q;$<=itTB9oQ{$@HZ;PwkSj+Pt za5xH&I>e2!*?3^qJ#FCFp&F6#{!^0&@?c)0SO~uVz>Y~rUTY%(tvQz|NOuG-Tm8dIGDBop}>?_yCH+-aa}lo5}5^;(EgJR7b1mdSY`T2 z;%Lz5A?A7wX=e{uE>&E@Lc~L^BaYH=1Bn9k=~_a57;Mbpq-ew`3=nR4U_}V7O!R;a zyr4jK0+_fZq75$Xpck2miL%$;0at{w*MgTrBQF(X5BOV8WKo9!$xtiav@5MPMzwMU zlAy%0xF3yn@H}}H`NW@T@wt)M(X5)6thZr{8r^PF$q9Yfgrg;Mj$HY0e5YhpXCq9- z1uB{Y(%uy3%m-y3~X;KoW&h~CaA2p?m)@kd#K8$*W}4nIIV zhV$UH@ILTv9CQdmp(@O@cq5|$JM6j1X+f3&zKSzYqjW@1adhG71Z0bx?Elm6?mv0M z4}j3{Dzu|8htvmg_2xhbR_LawGjSrM%mL1QkUlG|EYvvY+=qC_x1A0%k2$enEiw~> zQD*dTY&r;9Odz=tUnSpM2r9VJRJwkGMz{yG$@q1$C>a8F+Y0S)_%T)!2NUl=86-gB zA;d#+*hx|nPsjQ#9dXn^xH%%oJz*IZ3Yqc_TqwpD4F%3p8K($eyHXH>hErrzFb1>W z+nKLt)){7iPcYtuIH*hqgit zV+mw$n2w+C-ng1xj;y!>H6$AjN@lWB3KJ=?^A1GNbO;AqPJnWQZut6Utwkbv7D1D9 z@tlC_W27pCV{i}=rbi`PVqcNqlIT&*rEBnzp;q+gdN|6XT#HL0x~BoH5ZEAg;BM1C z197I9;4464IH44z5s)|5*zt?ef328Glnu2*tM-$LBC3I}p3%H&g`dcq8r;O}PD{EAd=?yg}QJT6fznZs7%=+sED! zeh4bvWA$|^8@vM9JtV(1u!$SN$F*J2v`%0`;RJmi?CD*%3wr!(~@aT01%hv-SF zpUML)rVF(5X!PvPO~Y~c%F-LKJ_-g5WLhi_y^dh9cM4Bf(vmUppSt5s2bv7c>D{D; zMcBoOzPi_+b$34te{U^A&Qp9-dGz1#TO?XHCx$)#ecm3yd3+i;31}Q$`H^Li9&bgWJhP|56i_UZ5ilR??iW`Gyz~Cfxa}I-gNI;pf#+>iNjVpMy zzHvn%LV8o{JORp~39vCjK-r;M3J_^=Nl#Ka$mzHNawEn-$D|#H=aps@{aePt%bUW7`hX)3N0?}>m!jjX(uk7{ zE70LN9p=@Hy@3YDe7Y}hO|NkmlFf!sKwKi>&w6y+kg~yqaLSR4cRif-5_Zp!)7hsY z#pX9e*pBR3hsW&RNbsL#`v(1akO2(FYM zPP^zW(hZ^|fzCIbHtJMsQViJ7Aj+=)U&DsK@X-^aSC)RyG!U|f$FmWx@8@U%Xvh(o zgR$@ngAEOx0!OsuK|{`QVdrxBIrO-BzW=x8;zDT+ZMcCXE zILGqyc7J@d`~BHEa)9o~SURXPWJ8fS00f;M$Rbxle&BJ$23Wi)@*vc6J0MfR45?fX zXTo&a1LN>G295M$n0y8;XX)5}4^8Q2|7(SeL7DGEaldr-etd*$Gq}>B78yruKE>5Y z+mdG?gl6P3ilnJBL*V-hhNz6A<#0zw1W9)~h9|tWpaTOYiy7qM;;XkR=0gZ1&aTLX zsuM;#UD_KMS|8NpS|^d$q^f|bi-|>vIPDln4FtE_bp|nXY{mwpb2u$206bY znj{9?Z|7bgcTQ{oqlLaQsqBniv6XjR>D7Ib(hTEs(nCmeF!=L{5KmKE(gc@)6-TMy z!2DLjsX}KR+C_&o9!FVqiK%c*hf83#K_e8ODy({BWPd!!{ryQAvI9$W zSTy?j3X3l)`UtpxwioQL+tX3-dN*nq2V0+L0;*p~X(8Y5U5Bj5-%I;@W%WDv&wlxx z2HdTV5(X^*cdy&17CsJGM#47vWgz*vx^5RmJ3>DZa_QhoN0O&&HQ<-+gH@dU{{n-K zC4_ofN?HH_00v@9M??Vs0RI60puMM)00009a7bBm000XU000XU0RWnu7ytkO2XskI zMF->q1qBo-Td4E|000CvNklC~?2E>3mFfdgX6CEsAx>bnp5~fzE z$|ng*6sGpOL?~MaKpDE8m>3u;genH4u$i*b{ucYzwEJ=Lv>x(b%{qAR5cYEXU zSfGDqv)Q$M`)f<9_{hERJPylzWP$3Vn3ECILJVMA@G>v&z@v86Za;*d+Vgkxk+g^F0%KLm- ze)KAoJYZEhZyrr8bqshEPp#X9#l%q^H zHq(!NWA)KwUiiF0SDdR(i$3D!isvYRX{cCHWzxN7*LpghNGqCYqEuRu^zTcRZ+c$0 zI9?I(z*SzVziHEsj#7O_p#Xa3VJJgW-YBAE zeJjGY#A_P2ZMn(xyG*A2?@N1a6i<6itym!sjY0wBP^idN597;RfFYp&|N82&&Knd8 zAhU<6c_rs6#=>SK3I#iler7UtUkil-$f;1CJM<=|TmZXF*L*;s0CIY$$o(h06ATXh zOrJx6hXTmV-)H$mWWVLUhSLW-OxqFJ4GIO2(L-?20NE6(BDD$xvmU{aXuHxR z3I&iup;EKFFzXQviLUv8LILFTP&z}kr()J47y_YLk5cQ7p-=!h6e^@VPDbLLU~q(H z0Sn~~3I%W%9;&ZZ-U03-U00Jwp#XZsT?PKJh{*J6bzn1e;Tub4SOvb*Sanz8rJj-0 z$8}cD6Wd<3a?h&e-tTD|tg=3ccnwy8uf;z*D!^Aix+Aq6HulfA|mVcxaw*j9 z^B2`vv8Gg`$^2l`gT(xWUQIsbRZ=nUYdq~u^@zY3<9mBd&se{36|Jo&@pzMa9j{Hy z#PMp%G@8sy5s{1Y6G5Q>GI}UoR$cwu&s>LZ>hrUK$ID$#`)tOIeLZwHuGec63ZQTP Y0E89YwfjLv^#A|>07*qoM6N<$f+d+20{{R3 delta 981 zcmV;`11kL1G5iORBa>qk7JndPNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe z02gnPU&TfM00V+aL_t(|+U%V%Z`wc@hQCx9sty%HhfbB1DH)LZ12W{l&(w=ex7-h_Rn%F*$Qz>^pz=et)k)_+mUB3&dx! zSUl;MzZ-gpul4puM5L?N!F)cC8t)aCK0rJ!k1zID*NjF4*3;2ezx*L0GMFuIb^C+) zzQJsH=NT_bzkL7obhOb;TRs2%F3#7d&-YQ{tR_Pg3Lu$66|8Vg_W(`fkKXQb%A_m` zp~`{{X3NZ>K(79GMSn_l>}MR`MsL5kt_k?(JqNSp4IT>M)Cy(u00})uC6CaQcs8$M zD2TEslXAq%9EVoge?1-9luDWV4Q2i1bAIPGKMAhajhQEd*|Lv90i;$aLz!G2AoHo= zeaWw&b>pUrXY(#4D|)Fig?b{Fmn!gF%15axoL}sDZ)&{EGJhVoMM|}r4Erb)Kx8gN z39VzSLuEaFXc6Up&lV0*EKE$E4p&5_$re3QB90zn-|+S z8lUIAL!kf?DAZS-aUUJEeqoI+?z>SoT=AP0FCW*n-*sHR;}?2f%5j`oqVsIa{Hu_8 zG?eNQg#w7orGG6$J!*C3Sixc`LQ~@T#tq%C5}lVy)c;)Ot5&=M51#pWY!nJ0u|nCD z$dx5+*F6Ok3gDCq73(`u0B!VAC=|d;C{#(Uh{0j=t%pJqiU7TcO6|v51H)7K^QJ zduvguOt)u6?G;eDOt0(N+7hqyeZJ!#`?+2nif0>tWl^S|A|i*wp^riVq*AE;)%CGf zEL5r!+)YuDYU#6%XR8Qe^{?SgIuXy~|NW;-gG$-5FCwy<3 [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let upper_torso_mat = self.upper_torso.compute_base_matrix(); let shoulder_l_mat = self.shoulder_l.compute_base_matrix(); let shoulder_r_mat = self.shoulder_r.compute_base_matrix(); @@ -78,6 +78,8 @@ impl Skeleton for BipedLargeSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/bird_medium/mod.rs b/voxygen/src/anim/bird_medium/mod.rs index 0ff89377ae..d5e7832e74 100644 --- a/voxygen/src/anim/bird_medium/mod.rs +++ b/voxygen/src/anim/bird_medium/mod.rs @@ -27,7 +27,7 @@ impl BirdMediumSkeleton { impl Skeleton for BirdMediumSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -47,6 +47,8 @@ impl Skeleton for BirdMediumSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/bird_small/mod.rs b/voxygen/src/anim/bird_small/mod.rs index 610b63122a..763bb2fac8 100644 --- a/voxygen/src/anim/bird_small/mod.rs +++ b/voxygen/src/anim/bird_small/mod.rs @@ -31,7 +31,7 @@ impl BirdSmallSkeleton { impl Skeleton for BirdSmallSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -51,6 +51,8 @@ impl Skeleton for BirdSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/character/attack.rs b/voxygen/src/anim/character/attack.rs index 9ceb9343e8..3cc674e799 100644 --- a/voxygen/src/anim/character/attack.rs +++ b/voxygen/src/anim/character/attack.rs @@ -21,60 +21,65 @@ impl Animation for AttackAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let slow = (anim_time as f32 * 9.0).sin(); - let accel_med = 1.0 - (anim_time as f32 * 16.0).cos(); - let accel_slow = 1.0 - (anim_time as f32 * 12.0).cos(); - let accel_fast = 1.0 - (anim_time as f32 * 24.0).cos(); - let decel = (anim_time as f32 * 16.0).min(PI / 2.0).sin(); + let lab = 1.0; + let accel_med = 1.0 - (anim_time as f32 * 16.0 * lab as f32).cos(); + let accel_slow = 1.0 - (anim_time as f32 * 12.0 * lab as f32).cos(); + let accel_fast = 1.0 - (anim_time as f32 * 24.0 * lab as f32).cos(); + let decel = (anim_time as f32 * 16.0 * lab as f32).min(PI / 2.0).sin(); + + let slow = (((5.0) + / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 12.4).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 * 12.4).sin()); next.head.offset = Vec3::new( 0.0 + skeleton_attr.neck_right, - -2.0 + skeleton_attr.neck_forward, + -2.0 + skeleton_attr.neck_forward + decel * 0.8, skeleton_attr.neck_height + 21.0, ); - next.head.ori = Quaternion::rotation_z(decel * -0.25) - * Quaternion::rotation_x(0.0 + decel * -0.1) - * Quaternion::rotation_y(decel * 0.1); + next.head.ori = Quaternion::rotation_z(decel * 0.25) + * Quaternion::rotation_x(0.0 + decel * 0.1) + * Quaternion::rotation_y(decel * -0.1); next.head.scale = Vec3::one() * skeleton_attr.head_scale; next.chest.offset = Vec3::new(0.0, 0.0, 7.0); - next.chest.ori = Quaternion::rotation_x(0.0); + next.chest.ori = Quaternion::rotation_z(decel * -0.2) + * Quaternion::rotation_x(0.0 + decel * -0.2) + * Quaternion::rotation_y(decel * 0.2); next.chest.scale = Vec3::one(); next.belt.offset = Vec3::new(0.0, 0.0, 5.0); - next.belt.ori = Quaternion::rotation_x(0.0); + next.belt.ori = Quaternion::rotation_z(decel * -0.1) + * Quaternion::rotation_x(0.0 + decel * -0.1) + * Quaternion::rotation_y(decel * 0.1); next.belt.scale = Vec3::one(); next.shorts.offset = Vec3::new(0.0, 0.0, 2.0); - next.shorts.ori = Quaternion::rotation_x(0.0); + next.belt.ori = Quaternion::rotation_z(decel * -0.08) + * Quaternion::rotation_x(0.0 + decel * -0.08) + * Quaternion::rotation_y(decel * 0.08); next.shorts.scale = Vec3::one(); match active_tool_kind { //TODO: Inventory Some(Tool::Sword) => { - next.l_hand.offset = - Vec3::new(-8.0 + accel_slow * 10.0, 8.0 + accel_fast * 3.0, 0.0); - next.l_hand.ori = Quaternion::rotation_z(-0.8) - * Quaternion::rotation_x(0.0 + accel_med * -0.8) - * Quaternion::rotation_y(0.0 + accel_med * -0.4); - next.l_hand.scale = Vec3::one() * 1.01; - - next.r_hand.offset = - Vec3::new(-8.0 + accel_slow * 10.0, 8.0 + accel_fast * 3.0, -2.0); - next.r_hand.ori = Quaternion::rotation_z(-0.8) - * Quaternion::rotation_x(0.0 + accel_med * -0.8) - * Quaternion::rotation_y(0.0 + accel_med * -0.4); - next.r_hand.scale = Vec3::one() * 1.01; - - next.main.offset = Vec3::new( - -8.0 + accel_slow * 10.0 + skeleton_attr.weapon_x, - 8.0 + accel_fast * 3.0, - 0.0, - ); - next.main.ori = Quaternion::rotation_z(-0.8) - * Quaternion::rotation_x(0.0 + accel_med * -0.8) - * Quaternion::rotation_y(0.0 + accel_med * -0.4); + next.l_hand.offset = Vec3::new(0.0, 1.0, 0.0); + next.l_hand.ori = Quaternion::rotation_x(1.27); + next.l_hand.scale = Vec3::one() * 1.04; + next.r_hand.offset = Vec3::new(0.0, 0.0, -3.0); + next.r_hand.ori = Quaternion::rotation_x(1.27); + next.r_hand.scale = Vec3::one() * 1.05; + next.main.offset = Vec3::new(0.0, 6.0, -1.0); + next.main.ori = Quaternion::rotation_x(-0.3) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); next.main.scale = Vec3::one(); + + next.control.offset = Vec3::new(-8.0 - slow * 1.0, 3.0 - slow * 5.0, 0.0); + next.control.ori = Quaternion::rotation_x(-1.2) + * Quaternion::rotation_y(slow * 1.5) + * Quaternion::rotation_z(1.4 + slow * 0.5); + next.control.scale = Vec3::one(); }, Some(Tool::Axe) => { next.l_hand.offset = @@ -266,10 +271,17 @@ impl Animation for AttackAnimation { next.lantern.scale = Vec3::one() * 0.0; next.torso.offset = Vec3::new(0.0, -0.2, 0.1) * skeleton_attr.scaler; - next.torso.ori = Quaternion::rotation_z(decel * -0.2) - * Quaternion::rotation_x(0.0 + decel * -0.2) - * Quaternion::rotation_y(decel * 0.2); + next.torso.ori = + Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/block.rs b/voxygen/src/anim/character/block.rs index 17c1415641..53cff1dda0 100644 --- a/voxygen/src/anim/character/block.rs +++ b/voxygen/src/anim/character/block.rs @@ -63,21 +63,23 @@ impl Animation for BlockAnimation { match active_tool_kind { //TODO: Inventory Some(Tool::Sword) => { - next.l_hand.offset = Vec3::new(-6.0, 3.5, 0.0 + wave_ultra_slow * 1.0); - next.l_hand.ori = Quaternion::rotation_x(-0.3); - next.l_hand.scale = Vec3::one() * 1.01; - next.r_hand.offset = Vec3::new(-6.0, 3.0, -2.0); - next.r_hand.ori = Quaternion::rotation_x(-0.3); - next.r_hand.scale = Vec3::one() * 1.01; - next.main.offset = Vec3::new( - -6.0 + skeleton_attr.weapon_x, - 4.5 + skeleton_attr.weapon_y, - 0.0, - ); + next.l_hand.offset = Vec3::new(0.0, -5.0, -5.0); + next.l_hand.ori = Quaternion::rotation_x(1.27); + next.l_hand.scale = Vec3::one() * 1.04; + next.r_hand.offset = Vec3::new(0.0, -6.0, -8.0); + next.r_hand.ori = Quaternion::rotation_x(1.27); + next.r_hand.scale = Vec3::one() * 1.05; + next.main.offset = Vec3::new(0.0, 0.0, -6.0); next.main.ori = Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); next.main.scale = Vec3::one(); + + next.control.offset = Vec3::new(-8.0, 13.0, 8.0); + next.control.ori = Quaternion::rotation_x(0.2) + * Quaternion::rotation_y(0.4) + * Quaternion::rotation_z(-1.57); + next.control.scale = Vec3::one(); }, Some(Tool::Axe) => { next.l_hand.offset = Vec3::new( diff --git a/voxygen/src/anim/character/blockidle.rs b/voxygen/src/anim/character/blockidle.rs index 67af96d2a2..09ac00407d 100644 --- a/voxygen/src/anim/character/blockidle.rs +++ b/voxygen/src/anim/character/blockidle.rs @@ -62,21 +62,23 @@ impl Animation for BlockIdleAnimation { match active_tool_kind { //TODO: Inventory Some(Tool::Sword) => { - next.l_hand.offset = Vec3::new(-6.0, 3.5, 0.0 + wave_ultra_slow * 1.0); - next.l_hand.ori = Quaternion::rotation_x(-0.3); - next.l_hand.scale = Vec3::one() * 1.01; - next.r_hand.offset = Vec3::new(-6.0, 3.0, -2.0); - next.r_hand.ori = Quaternion::rotation_x(-0.3); - next.r_hand.scale = Vec3::one() * 1.01; - next.main.offset = Vec3::new( - -6.0 + skeleton_attr.weapon_x, - 4.5 + skeleton_attr.weapon_y, - 0.0, - ); + next.l_hand.offset = Vec3::new(0.0, -5.0, -5.0); + next.l_hand.ori = Quaternion::rotation_x(1.27); + next.l_hand.scale = Vec3::one() * 1.04; + next.r_hand.offset = Vec3::new(0.0, -6.0, -8.0); + next.r_hand.ori = Quaternion::rotation_x(1.27); + next.r_hand.scale = Vec3::one() * 1.05; + next.main.offset = Vec3::new(0.0, 0.0, -6.0); next.main.ori = Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); next.main.scale = Vec3::one(); + + next.control.offset = Vec3::new(-8.0, 13.0, 8.0); + next.control.ori = Quaternion::rotation_x(0.2) + * Quaternion::rotation_y(0.4) + * Quaternion::rotation_z(-1.57); + next.control.scale = Vec3::one(); }, Some(Tool::Axe) => { next.l_hand.offset = Vec3::new( diff --git a/voxygen/src/anim/character/charge.rs b/voxygen/src/anim/character/charge.rs index a0cbe277d5..18203270c9 100644 --- a/voxygen/src/anim/character/charge.rs +++ b/voxygen/src/anim/character/charge.rs @@ -103,6 +103,11 @@ impl Animation for ChargeAnimation { next.torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + next } } diff --git a/voxygen/src/anim/character/cidle.rs b/voxygen/src/anim/character/cidle.rs index c4093cf2c3..a33d6d0ed9 100644 --- a/voxygen/src/anim/character/cidle.rs +++ b/voxygen/src/anim/character/cidle.rs @@ -22,7 +22,7 @@ impl Animation for CidleAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let wave_ultra_slow = (anim_time as f32 * 3.0 + PI).sin(); + let wave_ultra_slow = (anim_time as f32 * 0.5 + PI).sin(); let wave_ultra_slow_cos = (anim_time as f32 * 3.0 + PI).cos(); let wave_slow_cos = (anim_time as f32 * 6.0 + PI).cos(); let wave_slow = (anim_time as f32 * 6.0 + PI).sin(); @@ -63,29 +63,23 @@ impl Animation for CidleAnimation { match active_tool_kind { //TODO: Inventory Some(Tool::Sword) => { - next.l_hand.offset = Vec3::new( - -6.0 + wave_ultra_slow_cos * 1.0, - -2.0 + wave_ultra_slow_cos * 0.5, - 1.0 + wave_ultra_slow * 1.0, - ); - next.l_hand.ori = Quaternion::rotation_x(1.27); - next.l_hand.scale = Vec3::one() * 1.0; - next.r_hand.offset = Vec3::new( - -6.0 + wave_ultra_slow_cos * 1.0, - -2.5 + wave_ultra_slow_cos * 0.5, - -1.0 + wave_ultra_slow * 1.0, - ); - next.r_hand.ori = Quaternion::rotation_x(1.27); - next.r_hand.scale = Vec3::one() * 1.01; - next.main.offset = Vec3::new( - -6.0 + skeleton_attr.weapon_x + wave_ultra_slow_cos * 1.0, - 5.5 + skeleton_attr.weapon_y + wave_ultra_slow_cos * 0.5, - 1.0 + wave_ultra_slow * 1.0, - ); - next.main.ori = Quaternion::rotation_x(-0.3) + next.l_hand.offset = Vec3::new(-0.25, -5.0, -5.0); + next.l_hand.ori = Quaternion::rotation_x(1.47) * Quaternion::rotation_y(-0.2); + next.l_hand.scale = Vec3::one() * 1.04; + next.r_hand.offset = Vec3::new(1.25, -5.5, -8.0); + next.r_hand.ori = Quaternion::rotation_x(1.47) * Quaternion::rotation_y(0.3); + next.r_hand.scale = Vec3::one() * 1.05; + next.main.offset = Vec3::new(0.0, 0.0, -6.0); + next.main.ori = Quaternion::rotation_x(-0.1) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); next.main.scale = Vec3::one(); + + next.control.offset = Vec3::new(-7.0, 6.0, 6.0); + next.control.ori = Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); + next.control.scale = Vec3::one(); }, Some(Tool::Axe) => { next.l_hand.offset = Vec3::new( diff --git a/voxygen/src/anim/character/climb.rs b/voxygen/src/anim/character/climb.rs index 769abeecf6..b95ec99a84 100644 --- a/voxygen/src/anim/character/climb.rs +++ b/voxygen/src/anim/character/climb.rs @@ -59,11 +59,11 @@ impl Animation for ClimbAnimation { * Quaternion::rotation_y(wave_test * 0.10); next.shorts.scale = Vec3::one(); - next.l_hand.offset = Vec3::new(-6.0, -0.25 + wave_testc * 1.5, 5.0 - wave_test * 4.0); + next.l_hand.offset = Vec3::new(-6.0, -0.25 + wave_testc * 1.5, 6.0 - wave_test * 4.0); next.l_hand.ori = Quaternion::rotation_x(2.2 + wave_testc * 0.5); next.l_hand.scale = Vec3::one(); - next.r_hand.offset = Vec3::new(6.0, -0.25 - wave_testc * 1.5, 5.0 + wave_test * 4.0); + next.r_hand.offset = Vec3::new(6.0, -0.25 - wave_testc * 1.5, 6.0 + wave_test * 4.0); next.r_hand.ori = Quaternion::rotation_x(2.2 - wave_testc * 0.5); next.r_hand.scale = Vec3::one(); @@ -75,15 +75,6 @@ impl Animation for ClimbAnimation { next.r_foot.ori = Quaternion::rotation_x(0.2 + wave_testc * 0.5); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = - Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_cos * 0.25); - next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(wave_cos * 0.15); next.l_shoulder.scale = Vec3::one() * 1.1; @@ -96,6 +87,23 @@ impl Animation for ClimbAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 18.0, + ); + next.main.ori = + Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_cos * 0.25); + next.main.scale = Vec3::one(); + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -104,6 +112,17 @@ impl Animation for ClimbAnimation { next.torso.ori = Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/crun.rs b/voxygen/src/anim/character/crun.rs index 59d1807a69..cf12ef8c56 100644 --- a/voxygen/src/anim/character/crun.rs +++ b/voxygen/src/anim/character/crun.rs @@ -43,23 +43,33 @@ impl Animation for WieldAnimation { match Tool::Bow { //TODO: Inventory - Tool::Sword => { - next.l_hand.offset = Vec3::new(-6.0, 3.75, 0.25); - next.l_hand.ori = Quaternion::rotation_x(-0.3); - next.l_hand.scale = Vec3::one() * 1.01; - next.r_hand.offset = Vec3::new(-6.0, 3.0, -2.0); - next.r_hand.ori = Quaternion::rotation_x(-0.3); - next.r_hand.scale = Vec3::one() * 1.01; - next.weapon.offset = Vec3::new( - -6.0 + skeleton_attr.weapon_x, - 4.0 + skeleton_attr.weapon_y, + Some(Tool::Sword) => { + next.l_hand.offset = Vec3::new(0.0, -5.0, -5.0); + next.l_hand.ori = Quaternion::rotation_x(1.27); + next.l_hand.scale = Vec3::one() * 1.04; + next.r_hand.offset = Vec3::new(0.0, -6.0, -8.0); + next.r_hand.ori = Quaternion::rotation_x(1.27); + next.r_hand.scale = Vec3::one() * 1.05; + next.main.offset = Vec3::new( 0.0, + 0.0, + -6.0, ); - next.weapon.ori = Quaternion::rotation_x(-0.3) + next.main.ori = Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); - next.weapon.scale = Vec3::one(); - } + next.main.scale = Vec3::one(); + + next.control.offset = Vec3::new( + -8.0, + 4.0, + 6.0, + ); + next.control.ori = Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); + next.control.scale = Vec3::one(); + }, Tool::Axe => { next.l_hand.offset = Vec3::new(-6.0, 3.5, 0.0); next.l_hand.ori = Quaternion::rotation_x(-0.3); diff --git a/voxygen/src/anim/character/gliding.rs b/voxygen/src/anim/character/gliding.rs index 955208a3d2..5eacea3e85 100644 --- a/voxygen/src/anim/character/gliding.rs +++ b/voxygen/src/anim/character/gliding.rs @@ -105,14 +105,6 @@ impl Animation for GlidingAnimation { ); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(0.0); next.l_shoulder.scale = Vec3::one() * 1.1; @@ -126,6 +118,22 @@ impl Animation for GlidingAnimation { Quaternion::rotation_x(1.0) * Quaternion::rotation_y(wave_very_slow_cos * 0.04); next.glider.scale = Vec3::one(); + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 15.0, + ); + next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.main.scale = Vec3::one(); + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -135,6 +143,17 @@ impl Animation for GlidingAnimation { * Quaternion::rotation_y(tilt * 16.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/idle.rs b/voxygen/src/anim/character/idle.rs index 87a709ab50..57949cb125 100644 --- a/voxygen/src/anim/character/idle.rs +++ b/voxygen/src/anim/character/idle.rs @@ -56,7 +56,7 @@ impl Animation for IdleAnimation { next.shorts.scale = Vec3::one(); next.l_hand.offset = Vec3::new( - -6.0, + -7.0, -0.25 + wave_ultra_slow_cos * 0.15, 5.0 + wave_ultra_slow * 0.5, ); @@ -65,7 +65,7 @@ impl Animation for IdleAnimation { next.l_hand.scale = Vec3::one(); next.r_hand.offset = Vec3::new( - 6.0, + 7.0, -0.25 + wave_ultra_slow_cos * 0.15, 5.0 + wave_ultra_slow * 0.5 + wave_ultra_slow_abs * -0.05, ); @@ -80,14 +80,6 @@ impl Animation for IdleAnimation { next.r_foot.ori = Quaternion::identity(); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one() + wave_ultra_slow_abs * -0.05; - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 5.0); next.l_shoulder.ori = Quaternion::rotation_x(0.0); next.l_shoulder.scale = (Vec3::one() + wave_ultra_slow_abs * -0.05) * 1.15; @@ -100,6 +92,22 @@ impl Animation for IdleAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 18.0, + ); + next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.main.scale = Vec3::one() + wave_ultra_slow_abs * -0.05; + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -108,6 +116,17 @@ impl Animation for IdleAnimation { next.torso.ori = Quaternion::rotation_x(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/jump.rs b/voxygen/src/anim/character/jump.rs index 7a93aa4f82..b19f3f5ba4 100644 --- a/voxygen/src/anim/character/jump.rs +++ b/voxygen/src/anim/character/jump.rs @@ -68,14 +68,6 @@ impl Animation for JumpAnimation { next.r_foot.ori = Quaternion::rotation_x(wave_stop * 1.2 + wave_slow * 0.2); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(wave_stop_alt * 0.3); next.l_shoulder.scale = Vec3::one() * 1.1; @@ -88,6 +80,22 @@ impl Animation for JumpAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 15.0, + ); + next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.main.scale = Vec3::one(); + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -96,6 +104,17 @@ impl Animation for JumpAnimation { next.torso.ori = Quaternion::rotation_x(-0.2); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 6ed75e27e2..727d0f0918 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -37,12 +37,16 @@ pub struct CharacterSkeleton { r_hand: Bone, l_foot: Bone, r_foot: Bone, - main: Bone, l_shoulder: Bone, r_shoulder: Bone, glider: Bone, + main: Bone, + second: Bone, lantern: Bone, torso: Bone, + control: Bone, + l_control: Bone, + r_control: Bone, } impl CharacterSkeleton { @@ -52,11 +56,16 @@ impl CharacterSkeleton { impl Skeleton for CharacterSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let chest_mat = self.chest.compute_base_matrix(); let torso_mat = self.torso.compute_base_matrix(); let l_hand_mat = self.l_hand.compute_base_matrix(); + let r_hand_mat = self.r_hand.compute_base_matrix(); + let control_mat = self.control.compute_base_matrix(); + let l_control_mat = self.l_control.compute_base_matrix(); + let r_control_mat = self.r_control.compute_base_matrix(); let main_mat = self.main.compute_base_matrix(); + let second_mat = self.second.compute_base_matrix(); let head_mat = self.head.compute_base_matrix(); [ @@ -64,18 +73,20 @@ impl Skeleton for CharacterSkeleton { FigureBoneData::new(torso_mat * chest_mat), FigureBoneData::new(torso_mat * self.belt.compute_base_matrix()), FigureBoneData::new(torso_mat * self.shorts.compute_base_matrix()), - FigureBoneData::new(torso_mat * chest_mat * l_hand_mat), - FigureBoneData::new(torso_mat * chest_mat * self.r_hand.compute_base_matrix()), + FigureBoneData::new(torso_mat * chest_mat * control_mat * l_control_mat * l_hand_mat), + FigureBoneData::new(torso_mat * chest_mat * control_mat * r_control_mat * r_hand_mat), FigureBoneData::new(torso_mat * self.l_foot.compute_base_matrix()), FigureBoneData::new(torso_mat * self.r_foot.compute_base_matrix()), - FigureBoneData::new(torso_mat * chest_mat * main_mat), FigureBoneData::new(torso_mat * chest_mat * self.l_shoulder.compute_base_matrix()), FigureBoneData::new(torso_mat * chest_mat * self.r_shoulder.compute_base_matrix()), FigureBoneData::new(torso_mat * self.glider.compute_base_matrix()), + FigureBoneData::new(torso_mat * chest_mat * control_mat * l_control_mat * main_mat), + FigureBoneData::new(torso_mat * chest_mat * control_mat * r_control_mat * second_mat), FigureBoneData::new(torso_mat * chest_mat * self.lantern.compute_base_matrix()), FigureBoneData::new(torso_mat), - FigureBoneData::default(), - FigureBoneData::default(), + FigureBoneData::new(control_mat), + FigureBoneData::new(l_control_mat), + FigureBoneData::new(r_control_mat), ] } @@ -88,12 +99,16 @@ impl Skeleton for CharacterSkeleton { self.r_hand.interpolate(&target.r_hand, dt); self.l_foot.interpolate(&target.l_foot, dt); self.r_foot.interpolate(&target.r_foot, dt); - self.main.interpolate(&target.main, dt); self.l_shoulder.interpolate(&target.l_shoulder, dt); self.r_shoulder.interpolate(&target.r_shoulder, dt); self.glider.interpolate(&target.glider, dt); + self.main.interpolate(&target.main, dt); + self.second.interpolate(&target.second, dt); self.lantern.interpolate(&target.lantern, dt); self.torso.interpolate(&target.torso, dt); + self.control.interpolate(&target.control, dt); + self.l_control.interpolate(&target.l_control, dt); + self.r_control.interpolate(&target.r_control, dt); } } diff --git a/voxygen/src/anim/character/roll.rs b/voxygen/src/anim/character/roll.rs index b9fd7fbfa1..5dc42b8eed 100644 --- a/voxygen/src/anim/character/roll.rs +++ b/voxygen/src/anim/character/roll.rs @@ -85,16 +85,6 @@ impl Animation for RollAnimation { next.r_foot.ori = Quaternion::rotation_x(wave * -0.4); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = Quaternion::rotation_y(2.5) - * Quaternion::rotation_z(1.57) - * Quaternion::rotation_x(0.0); - next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(0.0); next.l_shoulder.scale = Vec3::one() * 1.1; @@ -107,6 +97,24 @@ impl Animation for RollAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 15.0, + ); + next.main.ori = Quaternion::rotation_y(2.5) + * Quaternion::rotation_z(1.57) + * Quaternion::rotation_x(0.0); + next.main.scale = Vec3::one(); + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -115,6 +123,18 @@ impl Animation for RollAnimation { Vec3::new(0.0, 0.0, 0.1 + wave_dub * 16.0) / 11.0 * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_x(wave_slow * 6.5) * Quaternion::rotation_y(tilt); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index c4e2479248..a4c92e3bce 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -23,23 +23,36 @@ impl Animation for RunAnimation { let lab = 1.0; let long = (((5.0) - / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 1.2).sin()).powf(2.0 as f32))) + / (1.5 + 3.5 * ((anim_time as f32 * lab as f32 * 0.66).sin()).powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * lab as f32 * 1.2).sin()); - let short = (((5.0) - / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 2.4).sin()).powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * lab as f32 * 1.5).sin()); + * ((anim_time as f32 * lab as f32 * 0.66).sin()); - let wave_stop = (anim_time as f32 * 2.6).min(PI / 2.0).sin(); + let short = (((5.0) + / (1.5 + 3.5 * ((anim_time as f32 * lab as f32 * 1.32).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 * 1.32).sin()); + + let shortalt = (((5.0) + / (1.5 + + 3.5 + * ((anim_time as f32 * lab as f32 * 1.32 + PI / 2.0).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 * 1.32 + PI / 2.0).sin()); + + let foot = (((5.0) + / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 1.32).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 * 1.32).sin()); + + let wave_stop = (anim_time as f32 * 2.6).min(PI / 2.0 / 2.0).sin(); let head_look = Vec2::new( - ((global_time + anim_time) as f32 / 4.0) + ((global_time + anim_time) as f32 / 18.0) .floor() .mul(7331.0) .sin() * 0.2, - ((global_time + anim_time) as f32 / 4.0) + ((global_time + anim_time) as f32 / 18.0) .floor() .mul(1337.0) .sin() @@ -70,21 +83,21 @@ impl Animation for RunAnimation { next.head.scale = Vec3::one() * skeleton_attr.head_scale; next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + short * 1.1); - next.chest.ori = Quaternion::rotation_z(long * 0.2); + next.chest.ori = Quaternion::rotation_z(short * 0.2); next.chest.scale = Vec3::one(); next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + short * 1.1); - next.belt.ori = Quaternion::rotation_z(long * 0.35); + next.belt.ori = Quaternion::rotation_z(short * 0.35); next.belt.scale = Vec3::one(); next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + short * 1.1); - next.shorts.ori = Quaternion::rotation_z(long * 0.6); + next.shorts.ori = Quaternion::rotation_z(short * 0.6); next.shorts.scale = Vec3::one(); next.l_hand.offset = Vec3::new( -6.0 + wave_stop * -1.0, -0.25 + short * 2.0, - 5.0 - long * 1.5, + 5.0 + short * -1.5, ); next.l_hand.ori = Quaternion::rotation_x(0.8 + short * 1.2) * Quaternion::rotation_y(wave_stop * 0.1); @@ -93,20 +106,32 @@ impl Animation for RunAnimation { next.r_hand.offset = Vec3::new( 6.0 + wave_stop * 1.0, -0.25 + short * -2.0, - 5.0 + long * 1.5, + 5.0 + short * 1.5, ); next.r_hand.ori = Quaternion::rotation_x(0.8 + short * -1.2) * Quaternion::rotation_y(wave_stop * -0.1); next.r_hand.scale = Vec3::one(); - next.l_foot.offset = Vec3::new(-3.4, 0.0 + short * 1.0, 6.0); - next.l_foot.ori = Quaternion::rotation_x(-0.0 - short * 1.2); + next.l_foot.offset = Vec3::new(-3.4, foot * 1.0, 6.0); + next.l_foot.ori = Quaternion::rotation_x(foot * -1.2); next.l_foot.scale = Vec3::one(); - next.r_foot.offset = Vec3::new(3.4, short * -1.0, 6.0); - next.r_foot.ori = Quaternion::rotation_x(short * 1.2); + next.r_foot.offset = Vec3::new(3.4, foot * -1.0, 6.0); + next.r_foot.ori = Quaternion::rotation_x(foot * 1.2); next.r_foot.scale = Vec3::one(); + next.l_shoulder.offset = Vec3::new(-5.0, -1.0, 4.7); + next.l_shoulder.ori = Quaternion::rotation_x(short * 0.15); + next.l_shoulder.scale = Vec3::one() * 1.1; + + next.r_shoulder.offset = Vec3::new(5.0, -1.0, 4.7); + next.r_shoulder.ori = Quaternion::rotation_x(short * -0.15); + next.r_shoulder.scale = Vec3::one() * 1.1; + + next.glider.offset = Vec3::new(0.0, 5.0, 0.0); + next.glider.ori = Quaternion::rotation_y(0.0); + next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( -7.0 + skeleton_attr.weapon_x, -5.0 + skeleton_attr.weapon_y, @@ -115,28 +140,36 @@ impl Animation for RunAnimation { next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + short * 0.25); next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, -0.5, 4.7); - next.l_shoulder.ori = Quaternion::rotation_x(short * 0.15); - next.l_shoulder.scale = Vec3::one() * 1.1; - - next.r_shoulder.offset = Vec3::new(5.0, -0.5, 4.7); - next.r_shoulder.ori = Quaternion::rotation_x(long * 0.15); - next.r_shoulder.scale = Vec3::one() * 1.1; - - next.glider.offset = Vec3::new(0.0, 5.0, 0.0); - next.glider.ori = Quaternion::rotation_y(0.0); - next.glider.scale = Vec3::one() * 0.0; + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; next.lantern.offset = Vec3::new(0.0, 5.0, 0.0); next.lantern.ori = Quaternion::rotation_y(0.0); next.lantern.scale = Vec3::one() * 0.0; - next.torso.offset = Vec3::new(0.0, -0.3 + long * -0.08, 0.4) * skeleton_attr.scaler; + next.torso.offset = Vec3::new(0.0, -0.3 + shortalt * -0.065, 0.4) * skeleton_attr.scaler; next.torso.ori = - Quaternion::rotation_x(wave_stop * speed * -0.06 + wave_stop * speed * -0.005) + Quaternion::rotation_x(wave_stop * speed * -0.05 + wave_stop * speed * -0.005) * Quaternion::rotation_y(tilt); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); + next } } diff --git a/voxygen/src/anim/character/sit.rs b/voxygen/src/anim/character/sit.rs index 2a1d4858bd..a4a8f4a210 100644 --- a/voxygen/src/anim/character/sit.rs +++ b/voxygen/src/anim/character/sit.rs @@ -95,14 +95,6 @@ impl Animation for SitAnimation { ); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one() + wave_slow_abs * -0.05; - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(0.0); next.l_shoulder.scale = (Vec3::one() + wave_slow_abs * -0.05) * 1.15; @@ -115,6 +107,22 @@ impl Animation for SitAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 15.0, + ); + next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.main.scale = Vec3::one() + wave_slow_abs * -0.05; + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -122,6 +130,18 @@ impl Animation for SitAnimation { next.torso.offset = Vec3::new(0.0, -0.2, wave_stop * -0.16) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_x(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/stand.rs b/voxygen/src/anim/character/stand.rs index 2116115be7..116cf5db09 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -43,26 +43,25 @@ impl Animation for StandAnimation { next.head.scale = Vec3::one() * skeleton_attr.head_scale; next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + slow * 0.3); - next.chest.ori = Quaternion::rotation_x(0.0); - next.chest.scale = Vec3::one() * 1.01 + breathe * 0.05; + next.chest.ori = Quaternion::rotation_z(head_look.x * 0.6); + next.chest.scale = Vec3::one() * 1.01; next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + slow * 0.3); - next.belt.ori = Quaternion::rotation_x(0.0); + next.belt.ori = Quaternion::rotation_z(head_look.x * 0.4); next.belt.scale = Vec3::one() + breathe * 0.05; next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + slow * 0.3); next.shorts.ori = Quaternion::rotation_x(0.0); next.shorts.scale = Vec3::one(); - next.l_hand.offset = Vec3::new(-6.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5); + next.l_hand.offset = Vec3::new(-7.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5); next.l_hand.ori = Quaternion::rotation_x(0.0 + slow * -0.06); next.l_hand.scale = Vec3::one(); - next.r_hand.offset = - Vec3::new(6.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5 + breathe * -0.05); + next.r_hand.offset = Vec3::new(7.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5); next.r_hand.ori = Quaternion::rotation_x(0.0 + slow * -0.06); - next.r_hand.scale = Vec3::one() + breathe * -0.05; + next.r_hand.scale = Vec3::one(); next.l_foot.offset = Vec3::new(-3.4, -0.1, 8.0); next.l_foot.ori = Quaternion::identity(); @@ -72,14 +71,6 @@ impl Animation for StandAnimation { next.r_foot.ori = Quaternion::identity(); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); - next.main.scale = Vec3::one() + breathe * -0.05; - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 5.0); next.l_shoulder.ori = Quaternion::rotation_x(0.0); next.l_shoulder.scale = (Vec3::one() + breathe * -0.05) * 1.15; @@ -92,6 +83,22 @@ impl Animation for StandAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 18.0, + ); + next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.main.scale = Vec3::one(); + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); next.lantern.ori = Quaternion::rotation_x(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -100,6 +107,17 @@ impl Animation for StandAnimation { next.torso.ori = Quaternion::rotation_x(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/swim.rs b/voxygen/src/anim/character/swim.rs index 4531e0ae8a..8b8220277d 100644 --- a/voxygen/src/anim/character/swim.rs +++ b/voxygen/src/anim/character/swim.rs @@ -95,15 +95,6 @@ impl Animation for SwimAnimation { next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave_cos * 1.5); next.r_foot.scale = Vec3::one(); - next.main.offset = Vec3::new( - -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, - 15.0, - ); - next.main.ori = - Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_cos * 0.25); - next.main.scale = Vec3::one(); - next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(wave_cos * 0.15); next.l_shoulder.scale = Vec3::one() * 1.1; @@ -116,6 +107,23 @@ impl Animation for SwimAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; + next.main.offset = Vec3::new( + -7.0 + skeleton_attr.weapon_x, + -5.0 + skeleton_attr.weapon_y, + 18.0, + ); + next.main.ori = + Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_cos * 0.25); + next.main.scale = Vec3::one(); + + next.second.offset = Vec3::new( + 0.0 + skeleton_attr.weapon_x, + 0.0 + skeleton_attr.weapon_y, + 0.0, + ); + next.second.ori = Quaternion::rotation_y(0.0); + next.second.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(0.0, 5.0, 0.0); next.lantern.ori = Quaternion::rotation_y(0.0); next.lantern.scale = Vec3::one() * 0.0; @@ -126,6 +134,17 @@ impl Animation for SwimAnimation { * Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.control.offset = Vec3::new(0.0, 0.0, 0.0); + next.control.ori = Quaternion::rotation_x(0.0); + next.control.scale = Vec3::one(); + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/character/wield.rs b/voxygen/src/anim/character/wield.rs index 68d18d2c9d..9e882fed3c 100644 --- a/voxygen/src/anim/character/wield.rs +++ b/voxygen/src/anim/character/wield.rs @@ -30,21 +30,23 @@ impl Animation for WieldAnimation { match active_tool_kind { //TODO: Inventory Some(Tool::Sword) => { - next.l_hand.offset = Vec3::new(-6.0, -2.0, 1.0); + next.l_hand.offset = Vec3::new(0.0, -5.0, -5.0); next.l_hand.ori = Quaternion::rotation_x(1.27); - next.l_hand.scale = Vec3::one() * 1.00; - next.r_hand.offset = Vec3::new(-6.0, -2.5, -1.0); + next.l_hand.scale = Vec3::one() * 1.04; + next.r_hand.offset = Vec3::new(0.0, -6.0, -8.0); next.r_hand.ori = Quaternion::rotation_x(1.27); - next.r_hand.scale = Vec3::one() * 1.01; - next.main.offset = Vec3::new( - -6.0 + skeleton_attr.weapon_x, - 5.5 + skeleton_attr.weapon_y, - 1.0, - ); + next.r_hand.scale = Vec3::one() * 1.05; + next.main.offset = Vec3::new(0.0, 0.0, -6.0); next.main.ori = Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); next.main.scale = Vec3::one(); + + next.control.offset = Vec3::new(-8.0, 4.0, 6.0); + next.control.ori = Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); + next.control.scale = Vec3::one(); }, Some(Tool::Axe) => { next.l_hand.offset = Vec3::new(-6.5, -0.5, 6.0); @@ -200,6 +202,14 @@ impl Animation for WieldAnimation { next.torso.offset = Vec3::new(0.0, 0.3 + wave * -0.08, 0.4) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_x(wave_stop * -0.2); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.l_control.ori = Quaternion::rotation_x(0.0); + next.l_control.scale = Vec3::one(); + + next.r_control.offset = Vec3::new(0.0, 0.0, 0.0); + next.r_control.ori = Quaternion::rotation_x(0.0); + next.r_control.scale = Vec3::one(); next } } diff --git a/voxygen/src/anim/critter/mod.rs b/voxygen/src/anim/critter/mod.rs index 9677461239..4a2c05ef6d 100644 --- a/voxygen/src/anim/critter/mod.rs +++ b/voxygen/src/anim/critter/mod.rs @@ -32,7 +32,7 @@ impl CritterSkeleton { impl Skeleton for CritterSkeleton { type Attr = CritterAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -50,6 +50,8 @@ impl Skeleton for CritterSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/dragon/mod.rs b/voxygen/src/anim/dragon/mod.rs index c3bfec7e61..131d78dc24 100644 --- a/voxygen/src/anim/dragon/mod.rs +++ b/voxygen/src/anim/dragon/mod.rs @@ -49,7 +49,7 @@ impl DragonSkeleton { impl Skeleton for DragonSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let chest_front_mat = self.chest_front.compute_base_matrix(); let wing_in_l_mat = self.wing_in_l.compute_base_matrix(); let wing_in_r_mat = self.wing_in_r.compute_base_matrix(); @@ -72,6 +72,8 @@ impl Skeleton for DragonSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/fish_medium/mod.rs b/voxygen/src/anim/fish_medium/mod.rs index b085ed7220..5912719299 100644 --- a/voxygen/src/anim/fish_medium/mod.rs +++ b/voxygen/src/anim/fish_medium/mod.rs @@ -35,7 +35,7 @@ impl FishMediumSkeleton { impl Skeleton for FishMediumSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let torso_mat = self.torso.compute_base_matrix(); let rear_mat = self.rear.compute_base_matrix(); @@ -56,6 +56,8 @@ impl Skeleton for FishMediumSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/fish_small/mod.rs b/voxygen/src/anim/fish_small/mod.rs index ed26ef0247..e1b47513f7 100644 --- a/voxygen/src/anim/fish_small/mod.rs +++ b/voxygen/src/anim/fish_small/mod.rs @@ -27,7 +27,7 @@ impl FishSmallSkeleton { impl Skeleton for FishSmallSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -47,6 +47,8 @@ impl Skeleton for FishSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/fixture/mod.rs b/voxygen/src/anim/fixture/mod.rs index 92a68d0125..a243b923a4 100644 --- a/voxygen/src/anim/fixture/mod.rs +++ b/voxygen/src/anim/fixture/mod.rs @@ -13,7 +13,7 @@ impl FixtureSkeleton { impl Skeleton for FixtureSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { [ FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), @@ -31,6 +31,8 @@ impl Skeleton for FixtureSkeleton { FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), + FigureBoneData::new(vek::Mat4::identity()), + FigureBoneData::new(vek::Mat4::identity()), ] } diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 1a309e95c9..347df6f7e6 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -51,7 +51,7 @@ impl Bone { pub trait Skeleton: Send + Sync + 'static { type Attr; - fn compute_matrices(&self) -> [FigureBoneData; 16]; + fn compute_matrices(&self) -> [FigureBoneData; 18]; /// Change the current skeleton to be more like `target`. fn interpolate(&mut self, target: &Self, dt: f32); diff --git a/voxygen/src/anim/object/mod.rs b/voxygen/src/anim/object/mod.rs index d6f3ac510e..b9befc2522 100644 --- a/voxygen/src/anim/object/mod.rs +++ b/voxygen/src/anim/object/mod.rs @@ -15,7 +15,7 @@ const SCALE: f32 = 1.0 / 11.0; impl Skeleton for ObjectSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { [ FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), @@ -33,6 +33,8 @@ impl Skeleton for ObjectSkeleton { FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), + FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), + FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), ] } diff --git a/voxygen/src/anim/quadruped_medium/mod.rs b/voxygen/src/anim/quadruped_medium/mod.rs index 57f36c9c1f..a30eaa0203 100644 --- a/voxygen/src/anim/quadruped_medium/mod.rs +++ b/voxygen/src/anim/quadruped_medium/mod.rs @@ -31,7 +31,7 @@ impl QuadrupedMediumSkeleton { impl Skeleton for QuadrupedMediumSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { let ears_mat = self.ears.compute_base_matrix(); let head_upper_mat = self.head_upper.compute_base_matrix(); let head_lower_mat = self.head_lower.compute_base_matrix(); @@ -53,6 +53,8 @@ impl Skeleton for QuadrupedMediumSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/quadruped_small/mod.rs b/voxygen/src/anim/quadruped_small/mod.rs index fb4c559fbf..a277b5aff8 100644 --- a/voxygen/src/anim/quadruped_small/mod.rs +++ b/voxygen/src/anim/quadruped_small/mod.rs @@ -26,7 +26,7 @@ impl QuadrupedSmallSkeleton { impl Skeleton for QuadrupedSmallSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 16] { + fn compute_matrices(&self) -> [FigureBoneData; 18] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -44,6 +44,8 @@ impl Skeleton for QuadrupedSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), ] } diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index fdf0aef8cd..dcace2126d 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -172,19 +172,6 @@ impl FigureModelCache { }, CameraMode::FirstPerson => None, }, - if camera_mode != CameraMode::FirstPerson - || character_state - .map(|cs| { - cs.action.is_attack() - || cs.action.is_block() - || cs.action.is_wield() - }) - .unwrap_or_default() - { - Some(mesh_main(equipment.and_then(|e| e.main.as_ref()))) - } else { - None - }, match camera_mode { CameraMode::ThirdPerson => Some( humanoid_armor_shoulder_spec.mesh_left_shoulder(&body), @@ -198,6 +185,19 @@ impl FigureModelCache { CameraMode::FirstPerson => None, }, Some(mesh_glider()), + if camera_mode != CameraMode::FirstPerson + || character_state + .map(|cs| { + cs.action.is_attack() + || cs.action.is_block() + || cs.action.is_wield() + }) + .unwrap_or_default() + { + Some(mesh_main(equipment.and_then(|e| e.main.as_ref()))) + } else { + None + }, Some(mesh_lantern()), None, None, From 005bca62302f35eebe597367d782aeedd863bdeb Mon Sep 17 00:00:00 2001 From: Capucho Date: Sun, 1 Mar 2020 22:18:22 +0000 Subject: [PATCH 14/22] Groundwork for fixing #36 and rewrite of client timeouts so that they don't use Instant and Duration --- client/src/lib.rs | 37 +++++++++++++++++------------------- voxygen/src/hud/mod.rs | 16 ++++++++++++++-- voxygen/src/main.rs | 5 +++++ voxygen/src/menu/main/mod.rs | 8 +++----- voxygen/src/session.rs | 30 ++++++++++++++++------------- voxygen/src/singleplayer.rs | 29 ++++++++++++++++++++-------- 6 files changed, 77 insertions(+), 48 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 57ee0fb4d7..5bb2068dc6 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -40,11 +40,11 @@ use vek::*; // The duration of network inactivity until the player is kicked // @TODO: in the future, this should be configurable on the server // and be provided to the client -const SERVER_TIMEOUT: Duration = Duration::from_secs(20); +const SERVER_TIMEOUT: f64 = 20.0; // After this duration has elapsed, the user will begin getting kick warnings in // their chat window -const SERVER_TIMEOUT_GRACE_PERIOD: Duration = Duration::from_secs(14); +const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0; pub enum Event { Chat { @@ -64,8 +64,8 @@ pub struct Client { postbox: PostBox, - last_server_ping: Instant, - last_server_pong: Instant, + last_server_ping: f64, + last_server_pong: f64, last_ping_delta: f64, tick: u64, @@ -152,8 +152,8 @@ impl Client { postbox, - last_server_ping: Instant::now(), - last_server_pong: Instant::now(), + last_server_ping: 0.0, + last_server_pong: 0.0, last_ping_delta: 0.0, tick: 0, @@ -481,9 +481,9 @@ impl Client { } // Send a ping to the server once every second - if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) { + if self.state.get_time() - self.last_server_ping > 1. { self.postbox.send_message(ClientMsg::Ping); - self.last_server_ping = Instant::now(); + self.last_server_ping = self.state.get_time(); } // 6) Update the server about the player's physics attributes. @@ -528,16 +528,14 @@ impl Client { // Check that we have an valid connection. // Use the last ping time as a 1s rate limiter, we only notify the user once per // second - if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) { - let duration_since_last_pong = Instant::now().duration_since(self.last_server_pong); + if self.state.get_time() - self.last_server_ping > 1. { + let duration_since_last_pong = self.state.get_time() - self.last_server_pong; // Dispatch a notification to the HUD warning they will be kicked in {n} seconds - if duration_since_last_pong.as_secs() >= SERVER_TIMEOUT_GRACE_PERIOD.as_secs() { - if let Some(seconds_until_kick) = - SERVER_TIMEOUT.checked_sub(duration_since_last_pong) - { + if duration_since_last_pong >= SERVER_TIMEOUT_GRACE_PERIOD { + if self.state.get_time() - duration_since_last_pong > 0. { frontend_events.push(Event::DisconnectionNotification( - seconds_until_kick.as_secs(), + (self.state.get_time() - duration_since_last_pong).round() as u64, )); } } @@ -591,11 +589,10 @@ impl Client { ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong), ServerMsg::Pong => { - self.last_server_pong = Instant::now(); + self.last_server_pong = self.state.get_time(); - self.last_ping_delta = Instant::now() - .duration_since(self.last_server_ping) - .as_secs_f64(); + self.last_ping_delta = + (self.state.get_time() - self.last_server_ping).round(); }, ServerMsg::ChatMsg { message, chat_type } => { frontend_events.push(Event::Chat { message, chat_type }) @@ -712,7 +709,7 @@ impl Client { } else if let Some(err) = self.postbox.error() { return Err(err.into()); // We regularily ping in the tick method - } else if Instant::now().duration_since(self.last_server_pong) > SERVER_TIMEOUT { + } else if self.state.get_time() - self.last_server_pong > SERVER_TIMEOUT { return Err(Error::ServerTimeout); } Ok(frontend_events) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 760bd4a2ef..e4e0605839 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -357,7 +357,7 @@ impl Show { fn toggle_ui(&mut self) { self.ui = !self.ui; } - fn toggle_windows(&mut self) { + fn toggle_windows(&mut self, global_state: &mut GlobalState) { if self.bag || self.esc_menu || self.map @@ -379,9 +379,21 @@ impl Show { self.character_window = false; self.open_windows = Windows::None; self.want_grab = true; + + // Unpause the game if we are on singleplayer + if let Some(ref singleplayer) = global_state.singleplayer { + singleplayer.pause(false); + global_state.paused = false; + }; } else { self.esc_menu = true; self.want_grab = false; + + // Pause the game if we are on singleplayer + if let Some(ref singleplayer) = global_state.singleplayer { + singleplayer.pause(true); + global_state.paused = true; + }; } } @@ -1992,7 +2004,7 @@ impl Hud { self.ui.focus_widget(None); } else { // Close windows on esc - self.show.toggle_windows(); + self.show.toggle_windows(global_state); } true }, diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 53536055ce..8379044eea 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -32,6 +32,7 @@ use crate::{ menu::main::MainMenuState, meta::Meta, settings::Settings, + singleplayer::Singleplayer, window::Window, }; use common::assets::{load, load_expect}; @@ -45,6 +46,8 @@ pub struct GlobalState { window: Window, audio: AudioFrontend, info_message: Option, + singleplayer: Option, + paused: bool, } impl GlobalState { @@ -135,6 +138,8 @@ fn main() { settings, meta, info_message: None, + singleplayer: None, + paused: false, }; // Try to load the localization and log missing entries diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 9ce56b48dc..504e3d6dc9 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi}; pub struct MainMenuState { main_menu_ui: MainMenuUi, - singleplayer: Option, } impl MainMenuState { @@ -26,7 +25,6 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), - singleplayer: None, } } } @@ -47,7 +45,7 @@ impl PlayState for MainMenuState { } // Reset singleplayer server if it was running already - self.singleplayer = None; + global_state.singleplayer = None; loop { // Handle window events. @@ -119,7 +117,7 @@ impl PlayState for MainMenuState { // client_init contains Some(ClientInit), which spawns a thread which // contains a TcpStream::connect() call This call is // blocking TODO fix when the network rework happens - self.singleplayer = None; + global_state.singleplayer = None; client_init = None; self.main_menu_ui.cancel_connection(); }, @@ -127,7 +125,7 @@ impl PlayState for MainMenuState { MainMenuEvent::StartSingleplayer => { let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool - self.singleplayer = Some(singleplayer); + global_state.singleplayer = Some(singleplayer); attempt_login( global_state, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index a45ab1ad0f..cd79db199d 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -379,13 +379,15 @@ impl PlayState for SessionState { self.inputs.look_dir = cam_dir; - // Perform an in-game tick. - if let Err(err) = self.tick(clock.get_avg_delta()) { - global_state.info_message = - Some(localized_strings.get("common.connection_lost").to_owned()); - error!("[session] Failed to tick the scene: {:?}", err); + if !global_state.paused { + // Perform an in-game tick. + if let Err(err) = self.tick(clock.get_avg_delta()) { + global_state.info_message = + Some(localized_strings.get("common.connection_lost").to_owned()); + error!("[session] Failed to tick the scene: {:?}", err); - return PlayStateResult::Pop; + return PlayStateResult::Pop; + } } // Maintain global state. @@ -609,13 +611,15 @@ impl PlayState for SessionState { } } - // Maintain the scene. - self.scene.maintain( - global_state.window.renderer_mut(), - &mut global_state.audio, - &self.client.borrow(), - global_state.settings.graphics.gamma, - ); + if !global_state.paused { + // Maintain the scene. + self.scene.maintain( + global_state.window.renderer_mut(), + &mut global_state.audio, + &self.client.borrow(), + global_state.settings.graphics.gamma, + ); + } // Render the session. self.render(global_state.window.renderer_mut()); diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index a1b3cec031..c03e79457d 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -12,6 +12,7 @@ const TPS: u64 = 30; enum Msg { Stop, + Pause(bool), } /// Used to start and stop the background thread running the server @@ -50,6 +51,8 @@ impl Singleplayer { settings, ) } + + pub fn pause(&self, paused: bool) { let _ = self.sender.send(Msg::Pause(paused)); } } impl Drop for Singleplayer { @@ -64,8 +67,26 @@ fn run_server(mut server: Server, rec: Receiver) { // Set up an fps clock let mut clock = Clock::start(); + let mut paused = false; loop { + match rec.try_recv() { + Ok(msg) => match msg { + Msg::Stop => break, + Msg::Pause(val) => paused = val, + }, + Err(err) => match err { + TryRecvError::Empty => (), + TryRecvError::Disconnected => break, + }, + } + + if paused { + // Wait for the next tick. + clock.tick(Duration::from_millis(1000 / TPS)); + continue; + } + let events = server .tick(Input::default(), clock.get_last_delta()) .expect("Failed to tick server!"); @@ -81,14 +102,6 @@ fn run_server(mut server: Server, rec: Receiver) { // Clean up the server after a tick. server.cleanup(); - match rec.try_recv() { - Ok(_msg) => break, - Err(err) => match err { - TryRecvError::Empty => (), - TryRecvError::Disconnected => break, - }, - } - // Wait for the next tick. clock.tick(Duration::from_millis(1000 / TPS)); } From 218b75616e7e09f3aad9407af6e5e570cd4bff9d Mon Sep 17 00:00:00 2001 From: Capucho Date: Tue, 3 Mar 2020 19:51:15 +0000 Subject: [PATCH 15/22] Moved paused from GlobalState to SinglePlayer to prevent errors and unpauses now works using the resume button --- voxygen/src/hud/mod.rs | 11 +++++++---- voxygen/src/main.rs | 2 -- voxygen/src/session.rs | 10 ++++++++-- voxygen/src/singleplayer.rs | 24 ++++++++++++++++++------ 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index e4e0605839..7b6e2ee78b 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -381,18 +381,16 @@ impl Show { self.want_grab = true; // Unpause the game if we are on singleplayer - if let Some(ref singleplayer) = global_state.singleplayer { + if let Some(singleplayer) = global_state.singleplayer.as_ref() { singleplayer.pause(false); - global_state.paused = false; }; } else { self.esc_menu = true; self.want_grab = false; // Pause the game if we are on singleplayer - if let Some(ref singleplayer) = global_state.singleplayer { + if let Some(singleplayer) = global_state.singleplayer.as_ref() { singleplayer.pause(true); - global_state.paused = true; }; } } @@ -1929,6 +1927,11 @@ impl Hud { self.show.esc_menu = false; self.show.want_grab = false; self.force_ungrab = true; + + // Unpause the game if we are on singleplayer + if let Some(singleplayer) = global_state.singleplayer.as_ref() { + singleplayer.pause(false); + }; }, Some(esc_menu::Event::Logout) => { events.push(Event::Logout); diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 8379044eea..6cb1ec4e62 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -47,7 +47,6 @@ pub struct GlobalState { audio: AudioFrontend, info_message: Option, singleplayer: Option, - paused: bool, } impl GlobalState { @@ -139,7 +138,6 @@ fn main() { meta, info_message: None, singleplayer: None, - paused: false, }; // Try to load the localization and log missing entries diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index cd79db199d..7670eb1b1e 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -379,7 +379,10 @@ impl PlayState for SessionState { self.inputs.look_dir = cam_dir; - if !global_state.paused { + // Runs if either in a multiplayer server or the singleplayer server is unpaused + if global_state.singleplayer.is_none() + || !global_state.singleplayer.as_ref().unwrap().is_paused() + { // Perform an in-game tick. if let Err(err) = self.tick(clock.get_avg_delta()) { global_state.info_message = @@ -611,7 +614,10 @@ impl PlayState for SessionState { } } - if !global_state.paused { + // Runs if either in a multiplayer server or the singleplayer server is unpaused + if global_state.singleplayer.is_none() + || !global_state.singleplayer.as_ref().unwrap().is_paused() + { // Maintain the scene. self.scene.maintain( global_state.window.renderer_mut(), diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index c03e79457d..d2538c6bf6 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -4,6 +4,7 @@ use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError}; use log::info; use server::{Event, Input, Server, ServerSettings}; use std::{ + sync::atomic::{AtomicBool, Ordering}, thread::{self, JoinHandle}, time::Duration, }; @@ -20,6 +21,8 @@ enum Msg { pub struct Singleplayer { _server_thread: JoinHandle<()>, sender: Sender, + // Wether the server is stopped or not + paused: AtomicBool, } impl Singleplayer { @@ -47,12 +50,21 @@ impl Singleplayer { Singleplayer { _server_thread: thread, sender, + paused: AtomicBool::new(false), }, settings, ) } - pub fn pause(&self, paused: bool) { let _ = self.sender.send(Msg::Pause(paused)); } + /// Returns wether or not the server is paused + pub fn is_paused(&self) -> bool { self.paused.load(Ordering::Relaxed) } + + /// Pauses if true is passed and unpauses if false (Does nothing if in that + /// state already) + pub fn pause(&self, state: bool) { + self.paused.load(Ordering::SeqCst); + let _ = self.sender.send(Msg::Pause(state)); + } } impl Drop for Singleplayer { @@ -70,6 +82,7 @@ fn run_server(mut server: Server, rec: Receiver) { let mut paused = false; loop { + // Check any event such as stopping and pausing match rec.try_recv() { Ok(msg) => match msg { Msg::Stop => break, @@ -81,9 +94,11 @@ fn run_server(mut server: Server, rec: Receiver) { }, } + // Wait for the next tick. + clock.tick(Duration::from_millis(1000 / TPS)); + + // Skip updating the server if it's paused if paused { - // Wait for the next tick. - clock.tick(Duration::from_millis(1000 / TPS)); continue; } @@ -101,8 +116,5 @@ fn run_server(mut server: Server, rec: Receiver) { // Clean up the server after a tick. server.cleanup(); - - // Wait for the next tick. - clock.tick(Duration::from_millis(1000 / TPS)); } } From 1a504f5a6739b83721a511880e195a2d80b64d63 Mon Sep 17 00:00:00 2001 From: Capucho Date: Tue, 3 Mar 2020 21:26:59 +0000 Subject: [PATCH 16/22] Fixed the erroneous load on pause --- voxygen/src/session.rs | 4 ++++ voxygen/src/singleplayer.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7670eb1b1e..ba92e95cdd 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -383,6 +383,10 @@ impl PlayState for SessionState { if global_state.singleplayer.is_none() || !global_state.singleplayer.as_ref().unwrap().is_paused() { + log::warn!( + "{}", + global_state.singleplayer.as_ref().unwrap().is_paused() + ); // Perform an in-game tick. if let Err(err) = self.tick(clock.get_avg_delta()) { global_state.info_message = diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index d2538c6bf6..28c7165148 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -57,12 +57,12 @@ impl Singleplayer { } /// Returns wether or not the server is paused - pub fn is_paused(&self) -> bool { self.paused.load(Ordering::Relaxed) } + pub fn is_paused(&self) -> bool { self.paused.load(Ordering::SeqCst) } /// Pauses if true is passed and unpauses if false (Does nothing if in that /// state already) pub fn pause(&self, state: bool) { - self.paused.load(Ordering::SeqCst); + self.paused.store(state, Ordering::SeqCst); let _ = self.sender.send(Msg::Pause(state)); } } From 3a5b2c81cef5bd903a1f487c033216c95dc6082a Mon Sep 17 00:00:00 2001 From: Capucho Date: Tue, 3 Mar 2020 22:07:49 +0000 Subject: [PATCH 17/22] Removed logging, added the changes to the changelog and fixed the logout button --- CHANGELOG.md | 1 + voxygen/src/hud/mod.rs | 5 +++++ voxygen/src/session.rs | 4 ---- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 227c50b450..cd4f119c21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added sfx for collecting, dropping and using inventory items - New attack animation - weapon control system +- Game pauses when in singleplayer and pause menu ### Changed diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 7b6e2ee78b..25d41537fe 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1934,6 +1934,11 @@ impl Hud { }; }, Some(esc_menu::Event::Logout) => { + // Unpause the game if we are on singleplayer so that we can logout + if let Some(singleplayer) = global_state.singleplayer.as_ref() { + singleplayer.pause(false); + }; + events.push(Event::Logout); }, Some(esc_menu::Event::Quit) => events.push(Event::Quit), diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index ba92e95cdd..7670eb1b1e 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -383,10 +383,6 @@ impl PlayState for SessionState { if global_state.singleplayer.is_none() || !global_state.singleplayer.as_ref().unwrap().is_paused() { - log::warn!( - "{}", - global_state.singleplayer.as_ref().unwrap().is_paused() - ); // Perform an in-game tick. if let Err(err) = self.tick(clock.get_avg_delta()) { global_state.info_message = From ee6cc91479ddae22c1180e77f6d272571357233a Mon Sep 17 00:00:00 2001 From: Capucho Date: Wed, 4 Mar 2020 20:42:22 +0000 Subject: [PATCH 18/22] Fixed the bugs in the settings tab and the character button in the escape menu and unpause when there is more than 1 player --- server/src/lib.rs | 2 ++ voxygen/src/hud/mod.rs | 18 ++++++++++++++++-- voxygen/src/singleplayer.rs | 28 +++++++++++++++------------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index 4d11efd110..d9f009a890 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -578,6 +578,8 @@ impl Server { .get(entity) .is_some() } + + pub fn number_of_players(&self) -> i64 { self.metrics.player_online.get() } } impl Drop for Server { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 25d41537fe..d3818494d2 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1735,7 +1735,14 @@ impl Hud { settings_window::Event::ToggleHelp => self.show.help = !self.show.help, settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug, settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab), - settings_window::Event::Close => self.show.settings(false), + settings_window::Event::Close => { + // Unpause the game if we are on singleplayer so that we can logout + if let Some(singleplayer) = global_state.singleplayer.as_ref() { + singleplayer.pause(false); + }; + + self.show.settings(false) + }, settings_window::Event::AdjustMousePan(sensitivity) => { events.push(Event::AdjustMousePan(sensitivity)); }, @@ -1942,7 +1949,14 @@ impl Hud { events.push(Event::Logout); }, Some(esc_menu::Event::Quit) => events.push(Event::Quit), - Some(esc_menu::Event::CharacterSelection) => events.push(Event::CharacterSelection), + Some(esc_menu::Event::CharacterSelection) => { + // Unpause the game if we are on singleplayer so that we can logout + if let Some(singleplayer) = global_state.singleplayer.as_ref() { + singleplayer.pause(false); + }; + + events.push(Event::CharacterSelection) + }, None => {}, } } diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index 28c7165148..a5e9e5a87d 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -4,7 +4,10 @@ use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError}; use log::info; use server::{Event, Input, Server, ServerSettings}; use std::{ - sync::atomic::{AtomicBool, Ordering}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, thread::{self, JoinHandle}, time::Duration, }; @@ -13,7 +16,6 @@ const TPS: u64 = 30; enum Msg { Stop, - Pause(bool), } /// Used to start and stop the background thread running the server @@ -22,7 +24,7 @@ pub struct Singleplayer { _server_thread: JoinHandle<()>, sender: Sender, // Wether the server is stopped or not - paused: AtomicBool, + paused: Arc, } impl Singleplayer { @@ -35,6 +37,9 @@ impl Singleplayer { let thread_pool = client.map(|c| c.thread_pool().clone()); let settings2 = settings.clone(); + let paused = Arc::new(AtomicBool::new(false)); + let paused1 = paused.clone(); + let thread = thread::spawn(move || { let server = Server::new(settings2).expect("Failed to create server instance!"); @@ -43,14 +48,14 @@ impl Singleplayer { None => server, }; - run_server(server, receiver); + run_server(server, receiver, paused1); }); ( Singleplayer { _server_thread: thread, sender, - paused: AtomicBool::new(false), + paused, }, settings, ) @@ -61,10 +66,7 @@ impl Singleplayer { /// Pauses if true is passed and unpauses if false (Does nothing if in that /// state already) - pub fn pause(&self, state: bool) { - self.paused.store(state, Ordering::SeqCst); - let _ = self.sender.send(Msg::Pause(state)); - } + pub fn pause(&self, state: bool) { self.paused.store(state, Ordering::SeqCst); } } impl Drop for Singleplayer { @@ -74,19 +76,17 @@ impl Drop for Singleplayer { } } -fn run_server(mut server: Server, rec: Receiver) { +fn run_server(mut server: Server, rec: Receiver, paused: Arc) { info!("Starting server-cli..."); // Set up an fps clock let mut clock = Clock::start(); - let mut paused = false; loop { // Check any event such as stopping and pausing match rec.try_recv() { Ok(msg) => match msg { Msg::Stop => break, - Msg::Pause(val) => paused = val, }, Err(err) => match err { TryRecvError::Empty => (), @@ -98,8 +98,10 @@ fn run_server(mut server: Server, rec: Receiver) { clock.tick(Duration::from_millis(1000 / TPS)); // Skip updating the server if it's paused - if paused { + if paused.load(Ordering::SeqCst) && server.number_of_players() < 2 { continue; + } else if server.number_of_players() > 1 { + paused.store(false, Ordering::SeqCst); } let events = server From 936f3709606751408bae5f17a52098f04d491b24 Mon Sep 17 00:00:00 2001 From: S Handley Date: Thu, 5 Mar 2020 19:26:07 +0000 Subject: [PATCH 19/22] Fixes https://gitlab.com/veloren/veloren/issues/484 partially by saving logs to a fixed place (defined in the settings file) --- voxygen/src/logging.rs | 22 ++++++++++++++++------ voxygen/src/settings.rs | 39 +++++++++++++++++++++++++++++++++++++-- voxygen/src/window.rs | 11 ++++++----- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/voxygen/src/logging.rs b/voxygen/src/logging.rs index 4085b3c30f..50ecbe6481 100644 --- a/voxygen/src/logging.rs +++ b/voxygen/src/logging.rs @@ -1,4 +1,5 @@ use fern::colors::{Color, ColoredLevelConfig}; +use std::fs; use crate::settings::Settings; @@ -39,12 +40,20 @@ pub fn init( )) }); - // Try to create the log file. - // Incase of it failing we simply print it out to the console. - let mut log_file_created = Ok(()); - match fern::log_file(&format!("voxygen-{}.log", time.format("%Y-%m-%d-%H"))) { - Ok(log_file) => file_cfg = file_cfg.chain(log_file), - Err(e) => log_file_created = Err(e), + // Try to create the logs file parent directories. + let mut log_file_created = fs::create_dir_all(&settings.log.logs_path); + + if log_file_created.is_ok() { + // Try to create the log file. + match fern::log_file( + settings + .log + .logs_path + .join(&format!("voxygen-{}.log", time.format("%Y-%m-%d-%H"))), + ) { + Ok(log_file) => file_cfg = file_cfg.chain(log_file), + Err(e) => log_file_created = Err(e), + } } let stdout_cfg = fern::Dispatch::new() @@ -65,6 +74,7 @@ pub fn init( .apply() .expect("Failed to setup logging!"); + // Incase that the log file creation failed simply print it to the console if let Err(e) = log_file_created { log::error!("Failed to create log file! {}", e); } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 8d339f2837..5bae056d85 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -5,7 +5,7 @@ use crate::{ ui::ScaleMode, window::KeyMouse, }; -use directories::ProjectDirs; +use directories::{ProjectDirs, UserDirs}; use glutin::{MouseButton, VirtualKeyCode}; use log::warn; use serde_derive::{Deserialize, Serialize}; @@ -177,10 +177,28 @@ pub struct Log { // Whether to create a log file or not. // Default is to create one. pub log_to_file: bool, + // The path on which the logs will be stored + pub logs_path: PathBuf, } impl Default for Log { - fn default() -> Self { Self { log_to_file: true } } + fn default() -> Self { + let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen") + .expect("System's $HOME directory path not found!"); + + // Chooses a path to store the logs by the following order: + // - The VOXYGEN_LOGS environment variable + // - The ProjectsDirs data local directory + // This only selects if there isn't already an entry in the settings file + let logs_path = std::env::var_os("VOXYGEN_LOGS") + .map(PathBuf::from) + .unwrap_or(proj_dirs.data_local_dir().join("logs")); + + Self { + log_to_file: true, + logs_path, + } + } } /// `GraphicsSettings` contains settings related to framerate and in-game @@ -273,10 +291,26 @@ pub struct Settings { // TODO: Remove at a later date, for dev testing pub logon_commands: Vec, pub language: LanguageSettings, + pub screenshots_path: PathBuf, } impl Default for Settings { fn default() -> Self { + let user_dirs = UserDirs::new().expect("System's $HOME directory path not found!"); + + // Chooses a path to store the screenshots by the following order: + // - The VOXYGEN_SCREENSHOT environment variable + // - The user's picture directory + // - The executable's directory + // This only selects if there isn't already an entry in the settings file + let screenshots_path = std::env::var_os("VOXYGEN_SCREENSHOT") + .map(PathBuf::from) + .or(user_dirs.picture_dir().map(|dir| dir.join("veloren"))) + .or(std::env::current_exe() + .ok() + .and_then(|dir| dir.parent().map(|val| PathBuf::from(val)))) + .expect("Couldn't choose a place to store the screenshots"); + Settings { controls: ControlSettings::default(), gameplay: GameplaySettings::default(), @@ -288,6 +322,7 @@ impl Default for Settings { send_logon_commands: false, logon_commands: Vec::new(), language: LanguageSettings::default(), + screenshots_path, } } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 1b43c15787..59c9b6c4ed 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -594,7 +594,7 @@ impl Window { }); if take_screenshot { - self.take_screenshot(); + self.take_screenshot(&settings); } if toggle_fullscreen { @@ -659,15 +659,16 @@ impl Window { pub fn send_supplement_event(&mut self, event: Event) { self.supplement_events.push(event) } - pub fn take_screenshot(&mut self) { + pub fn take_screenshot(&mut self, settings: &Settings) { match self.renderer.create_screenshot() { Ok(img) => { + let mut path = settings.screenshots_path.clone(); + std::thread::spawn(move || { - use std::{path::PathBuf, time::SystemTime}; + use std::time::SystemTime; // Check if folder exists and create it if it does not - let mut path = PathBuf::from("./screenshots"); if !path.exists() { - if let Err(err) = std::fs::create_dir(&path) { + if let Err(err) = std::fs::create_dir_all(&path) { warn!("Couldn't create folder for screenshot: {:?}", err); } } From 0feb44047ac5d70cac019a4f702190e1fed2600c Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 28 Feb 2020 22:59:11 -0500 Subject: [PATCH 20/22] Remove `Client` dependency from Scene types and audio managers, add an example for using voxygen as a library to renderer images of characters --- Cargo.lock | 1 + voxygen/Cargo.toml | 2 + voxygen/examples/character_renderer.rs | 58 ++++++++ voxygen/src/audio/music.rs | 11 +- voxygen/src/audio/sfx/event_mapper/mod.rs | 16 ++- .../audio/sfx/event_mapper/movement/mod.rs | 10 +- .../src/audio/sfx/event_mapper/progression.rs | 13 +- voxygen/src/audio/sfx/mod.rs | 18 ++- voxygen/src/hud/mod.rs | 12 +- voxygen/src/lib.rs | 84 +++++++++++- voxygen/src/main.rs | 85 +----------- voxygen/src/menu/char_selection/mod.rs | 31 +++-- voxygen/src/scene/camera.rs | 41 ++++-- voxygen/src/scene/figure/mod.rs | 43 +++--- voxygen/src/scene/mod.rs | 103 ++++++++------ .../scene.rs => scene/simple.rs} | 126 +++++++++++------- voxygen/src/scene/terrain.rs | 44 +++--- voxygen/src/session.rs | 37 +++-- 18 files changed, 460 insertions(+), 275 deletions(-) create mode 100644 voxygen/examples/character_renderer.rs rename voxygen/src/{menu/char_selection/scene.rs => scene/simple.rs} (66%) diff --git a/Cargo.lock b/Cargo.lock index 399d5b6a2f..083ae3808f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3377,6 +3377,7 @@ dependencies = [ "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git)", "treeculler 0.1.0 (git+https://gitlab.com/yusdacra/treeculler.git)", + "uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "veloren-client 0.5.0", "veloren-common 0.5.0", diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 7c3e6debb0..9ae1608213 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -63,6 +63,7 @@ chrono = "0.4.9" rust-argon2 = "0.5" bincode = "1.2" deunicode = "1.0" +uvth = "3.1.1" [target.'cfg(target_os = "macos")'.dependencies] dispatch = "0.1.4" @@ -74,6 +75,7 @@ winres = "0.1" criterion = "0.3" git2 = "0.10" world = { package = "veloren-world", path = "../world" } +gfx_window_glutin = { version = "0.31.0", features = ["headless"] } [[bench]] name = "meshing_benchmark" diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs new file mode 100644 index 0000000000..ae07183f5e --- /dev/null +++ b/voxygen/examples/character_renderer.rs @@ -0,0 +1,58 @@ +use common::{assets, comp}; +use gfx_window_glutin::init_headless; +use vek::*; +use veloren_voxygen::{render, scene::simple as scene}; + +fn main() { + // Setup renderer + let dim = (200u16, 300u16, 1, gfx::texture::AaMode::Single); + let events_loop = glutin::EventsLoop::new(); + let context = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) + .build_headless(&events_loop, (dim.0 as u32, dim.1 as u32).into()) + .expect("Failed to build headless context"); + + let (_context, device, factory, color_view, depth_view) = init_headless(context, dim); + + let mut renderer = render::Renderer::new( + device, + factory, + color_view, + depth_view, + render::AaMode::SsaaX4, + render::CloudMode::Regular, + render::FluidMode::Shiny, + ) + .unwrap(); + + // Create character + let body = comp::humanoid::Body::random(); + const STARTER_BOW: &str = "common.items.weapons.starter_bow"; + let equipment = comp::Equipment { + main: assets::load_cloned(STARTER_BOW).ok(), + alt: None, + }; + + // Setup scene (using the character selection screen `Scene`) + let mut scene = scene::Scene::new(&mut renderer, None); + let scene_data = scene::SceneData { + time: 1.0, + delta_time: 1.0, + tick: 0, + body: Some(body.clone()), + gamma: 1.0, + }; + scene.camera_mut().set_focus_pos(Vec3::unit_z() * 0.8); + scene.camera_mut().set_distance(1.5); + scene.camera_mut().update(0.0); + scene.maintain(&mut renderer, scene_data); + + // Render + renderer.clear(); + scene.render(&mut renderer, 0, Some(body), &equipment); + + renderer.flush(); + // Get image + let img = renderer.create_screenshot().unwrap(); + img.save("character.png").unwrap(); +} diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs index 7b46abd016..397472472f 100644 --- a/voxygen/src/audio/music.rs +++ b/voxygen/src/audio/music.rs @@ -1,6 +1,5 @@ use crate::audio::AudioFrontend; -use client::Client; -use common::assets; +use common::{assets, state::State}; use rand::{seq::IteratorRandom, thread_rng}; use serde::Deserialize; use std::time::Instant; @@ -44,18 +43,18 @@ impl MusicMgr { } } - pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) { + pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State) { if audio.music_enabled() && self.began_playing.elapsed().as_secs_f64() > self.next_track_change { - self.play_random_track(audio, client); + self.play_random_track(audio, state); } } - fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) { + fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State) { const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0; - let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32; + let game_time = (state.get_time_of_day() as u64 % 86400) as u32; let current_period_of_day = Self::get_current_day_period(game_time); let mut rng = thread_rng(); diff --git a/voxygen/src/audio/sfx/event_mapper/mod.rs b/voxygen/src/audio/sfx/event_mapper/mod.rs index 94905cb802..a745b4de67 100644 --- a/voxygen/src/audio/sfx/event_mapper/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/mod.rs @@ -1,11 +1,12 @@ mod movement; mod progression; +use common::state::State; + use movement::MovementEventMapper; use progression::ProgressionEventMapper; use super::SfxTriggers; -use client::Client; pub struct SfxEventMapper { progression_event_mapper: ProgressionEventMapper, @@ -20,8 +21,15 @@ impl SfxEventMapper { } } - pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { - self.progression_event_mapper.maintain(client, triggers); - self.movement_event_mapper.maintain(client, triggers); + pub fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + triggers: &SfxTriggers, + ) { + self.progression_event_mapper + .maintain(state, player_entity, triggers); + self.movement_event_mapper + .maintain(state, player_entity, triggers); } } diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 617092e8d6..d222042a64 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -3,10 +3,10 @@ /// from use crate::audio::sfx::{SfxTriggerItem, SfxTriggers}; -use client::Client; use common::{ comp::{ActionState, Body, CharacterState, Item, ItemKind, MovementState, Pos, Stats, Vel}, event::{EventBus, SfxEvent, SfxEventItem}, + state::State, }; use hashbrown::HashMap; use specs::{Entity as EcsEntity, Join, WorldExt}; @@ -31,13 +31,13 @@ impl MovementEventMapper { } } - pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { + pub fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) { const SFX_DIST_LIMIT_SQR: f32 = 20000.0; - let ecs = client.state().ecs(); + let ecs = state.ecs(); let player_position = ecs .read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); for (entity, pos, vel, body, stats, character) in ( @@ -97,7 +97,7 @@ impl MovementEventMapper { } } - self.cleanup(client.entity()); + self.cleanup(player_entity); } /// As the player explores the world, we track the last event of the nearby diff --git a/voxygen/src/audio/sfx/event_mapper/progression.rs b/voxygen/src/audio/sfx/event_mapper/progression.rs index 3edfcf1f7b..7c05db0360 100644 --- a/voxygen/src/audio/sfx/event_mapper/progression.rs +++ b/voxygen/src/audio/sfx/event_mapper/progression.rs @@ -2,10 +2,10 @@ /// and experience and emits associated SFX use crate::audio::sfx::SfxTriggers; -use client::Client; use common::{ comp::Stats, event::{EventBus, SfxEvent, SfxEventItem}, + state::State, }; use specs::WorldExt; @@ -30,13 +30,18 @@ impl ProgressionEventMapper { } } - pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { - let ecs = client.state().ecs(); + pub fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + triggers: &SfxTriggers, + ) { + let ecs = state.ecs(); // level and exp changes let next_state = ecs.read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(self.state.clone(), |stats| ProgressionState { level: stats.level.level(), exp: stats.exp.current(), diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 2af778a0ef..86771e86c2 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -4,11 +4,11 @@ mod event_mapper; use crate::audio::AudioFrontend; -use client::Client; use common::{ assets, comp::{Ori, Pos}, event::{EventBus, SfxEvent, SfxEventItem}, + state::State, }; use event_mapper::SfxEventMapper; use hashbrown::HashMap; @@ -50,23 +50,29 @@ impl SfxMgr { } } - pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) { + pub fn maintain( + &mut self, + audio: &mut AudioFrontend, + state: &State, + player_entity: specs::Entity, + ) { if !audio.sfx_enabled() { return; } - self.event_mapper.maintain(client, &self.triggers); + self.event_mapper + .maintain(state, player_entity, &self.triggers); - let ecs = client.state().ecs(); + let ecs = state.ecs(); let player_position = ecs .read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); let player_ori = ecs .read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); audio.set_listener_pos(&player_position, &player_ori); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index d3818494d2..f5a0a89ad8 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -38,7 +38,7 @@ use crate::{ ecs::comp as vcomp, i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, Consts, FluidMode, Globals, Renderer}, - scene::camera::Camera, + scene::camera::{self, Camera}, ui::{fonts::ConrodVoxygenFonts, Graphic, Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, GameInput}, GlobalState, @@ -2112,9 +2112,13 @@ impl Hud { self.ui.focus_widget(maybe_id); } let events = self.update_layout(client, global_state, debug_info, dt); - let (v_mat, p_mat, _) = camera.compute_dependents(client); - self.ui - .maintain(&mut global_state.window.renderer_mut(), Some(p_mat * v_mat)); + let camera::Dependents { + view_mat, proj_mat, .. + } = camera.dependents(); + self.ui.maintain( + &mut global_state.window.renderer_mut(), + Some(proj_mat * view_mat), + ); // Check if item images need to be reloaded self.item_imgs.reload_if_changed(&mut self.ui); diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 301aab5d19..56877b2131 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -1,6 +1,82 @@ -/// Used by benchmarks -pub mod mesh; -pub mod render; +#![deny(unsafe_code)] +#![feature(drain_filter)] +#![recursion_limit = "2048"] -// Used by tests +#[macro_use] +pub mod ui; +pub mod anim; +pub mod audio; +mod ecs; +pub mod error; +pub mod hud; pub mod i18n; +pub mod key_state; +pub mod logging; +pub mod menu; +pub mod mesh; +pub mod meta; +pub mod render; +pub mod scene; +pub mod session; +pub mod settings; +#[cfg(feature = "singleplayer")] +pub mod singleplayer; +pub mod window; + +// Reexports +pub use crate::error::Error; + +use crate::{ + audio::AudioFrontend, meta::Meta, settings::Settings, singleplayer::Singleplayer, + window::Window, +}; + +/// A type used to store state that is shared between all play states. +pub struct GlobalState { + pub settings: Settings, + pub meta: Meta, + pub window: Window, + pub audio: AudioFrontend, + pub info_message: Option, + pub singleplayer: Option, +} + +impl GlobalState { + /// Called after a change in play state has occurred (usually used to + /// reverse any temporary effects a state may have made). + pub fn on_play_state_changed(&mut self) { + self.window.grab_cursor(false); + self.window.needs_refresh_resize(); + } + + pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); } +} + +pub enum Direction { + Forwards, + Backwards, +} + +/// States can either close (and revert to a previous state), push a new state +/// on top of themselves, or switch to a totally different state. +pub enum PlayStateResult { + /// Pop all play states in reverse order and shut down the program. + Shutdown, + /// Close the current play state and pop it from the play state stack. + Pop, + /// Push a new play state onto the play state stack. + Push(Box), + /// Switch the current play state with a new play state. + Switch(Box), +} + +/// A trait representing a playable game state. This may be a menu, a game +/// session, the title screen, etc. +pub trait PlayState { + /// Play the state until some change of state is required (i.e: a menu is + /// opened or the game is closed). + fn play(&mut self, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult; + + /// Get a descriptive name for this state type. + fn name(&self) -> &'static str; +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 6cb1ec4e62..f2a091daf0 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,94 +1,21 @@ #![deny(unsafe_code)] -#![feature(drain_filter)] #![recursion_limit = "2048"] -#[macro_use] -pub mod ui; -pub mod anim; -pub mod audio; -mod ecs; -pub mod error; -pub mod hud; -pub mod i18n; -pub mod key_state; -mod logging; -pub mod menu; -pub mod mesh; -pub mod meta; -pub mod render; -pub mod scene; -pub mod session; -pub mod settings; -#[cfg(feature = "singleplayer")] -pub mod singleplayer; -pub mod window; - -// Reexports -pub use crate::error::Error; - -use crate::{ - audio::AudioFrontend, - i18n::{i18n_asset_key, VoxygenLocalization}, +use veloren_voxygen::{ + audio::{self, AudioFrontend}, + i18n::{self, i18n_asset_key, VoxygenLocalization}, + logging, menu::main::MainMenuState, meta::Meta, settings::Settings, - singleplayer::Singleplayer, window::Window, + Direction, GlobalState, PlayState, PlayStateResult, }; + use common::assets::{load, load_expect}; use log::{debug, error}; use std::{mem, panic, str::FromStr}; -/// A type used to store state that is shared between all play states. -pub struct GlobalState { - settings: Settings, - meta: Meta, - window: Window, - audio: AudioFrontend, - info_message: Option, - singleplayer: Option, -} - -impl GlobalState { - /// Called after a change in play state has occurred (usually used to - /// reverse any temporary effects a state may have made). - pub fn on_play_state_changed(&mut self) { - self.window.grab_cursor(false); - self.window.needs_refresh_resize(); - } - - pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); } -} - -pub enum Direction { - Forwards, - Backwards, -} - -/// States can either close (and revert to a previous state), push a new state -/// on top of themselves, or switch to a totally different state. -pub enum PlayStateResult { - /// Pop all play states in reverse order and shut down the program. - Shutdown, - /// Close the current play state and pop it from the play state stack. - Pop, - /// Push a new play state onto the play state stack. - Push(Box), - /// Switch the current play state with a new play state. - Switch(Box), -} - -/// A trait representing a playable game state. This may be a menu, a game -/// session, the title screen, etc. -pub trait PlayState { - /// Play the state until some change of state is required (i.e: a menu is - /// opened or the game is closed). - fn play(&mut self, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult; - - /// Get a descriptive name for this state type. - fn name(&self) -> &'static str; -} - fn main() { // Initialize logging. let term_log_level = std::env::var_os("VOXYGEN_LOG") diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 6dc28fb820..f7eaae27c2 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -1,16 +1,16 @@ -mod scene; mod ui; use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, + scene::simple::{self as scene, Scene}, session::SessionState, window::Event as WinEvent, Direction, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{assets, clock::Clock, comp, msg::ClientState}; +use common::{assets, clock::Clock, comp, msg::ClientState, state::DeltaTime}; use log::error; -use scene::Scene; +use specs::WorldExt; use std::{cell::RefCell, rc::Rc, time::Duration}; use ui::CharSelectionUi; @@ -26,7 +26,10 @@ impl CharSelectionState { Self { char_selection_ui: CharSelectionUi::new(global_state), client, - scene: Scene::new(global_state.window.renderer_mut()), + scene: Scene::new( + global_state.window.renderer_mut(), + Some("fixture.selection_bg"), + ), } } } @@ -95,17 +98,23 @@ impl PlayState for CharSelectionState { }); // Maintain the scene. - self.scene.maintain( - global_state.window.renderer_mut(), - &self.client.borrow(), - humanoid_body.clone(), - global_state.settings.graphics.gamma, - ); + { + let client = self.client.borrow(); + let scene_data = scene::SceneData { + time: client.state().get_time(), + delta_time: client.state().ecs().read_resource::().0, + tick: client.get_tick(), + body: humanoid_body.clone(), + gamma: global_state.settings.graphics.gamma, + }; + self.scene + .maintain(global_state.window.renderer_mut(), scene_data); + } // Render the scene. self.scene.render( global_state.window.renderer_mut(), - &self.client.borrow(), + self.client.borrow().get_tick(), humanoid_body.clone(), &comp::Equipment { main: self diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index a0d0d59e54..9eb279d3c5 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -1,4 +1,3 @@ -use client::Client; use common::vol::{ReadVol, Vox}; use std::f32::consts::PI; use treeculler::Frustum; @@ -22,6 +21,13 @@ impl Default for CameraMode { fn default() -> Self { Self::ThirdPerson } } +#[derive(Clone)] +pub struct Dependents { + pub view_mat: Mat4, + pub proj_mat: Mat4, + pub cam_pos: Vec3, +} + pub struct Camera { tgt_focus: Vec3, focus: Vec3, @@ -33,6 +39,8 @@ pub struct Camera { mode: CameraMode, last_time: Option, + + dependents: Dependents, } impl Camera { @@ -49,12 +57,18 @@ impl Camera { mode, last_time: None, + + dependents: Dependents { + view_mat: Mat4::identity(), + proj_mat: Mat4::identity(), + cam_pos: Vec3::zero(), + }, } } /// Compute the transformation matrices (view matrix and projection matrix) /// and position of the camera. - pub fn compute_dependents(&self, client: &Client) -> (Mat4, Mat4, Vec3) { + pub fn compute_dependents(&mut self, terrain: &impl ReadVol) { let dist = { let (start, end) = ( self.focus @@ -66,9 +80,7 @@ impl Camera { self.focus, ); - match client - .state() - .terrain() + match terrain .ray(start, end) .ignore_error() .max_iter(500) @@ -82,7 +94,7 @@ impl Camera { .max(0.0) }; - let view_mat = Mat4::::identity() + self.dependents.view_mat = Mat4::::identity() * Mat4::translation_3d(-Vec3::unit_z() * dist) * Mat4::rotation_z(self.ori.z) * Mat4::rotation_x(self.ori.y) @@ -90,20 +102,21 @@ impl Camera { * Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x()) * Mat4::translation_3d(-self.focus); - let proj_mat = Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE); + self.dependents.proj_mat = + Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE); // TODO: Make this more efficient. - let cam_pos = Vec3::from(view_mat.inverted() * Vec4::unit_w()); - - (view_mat, proj_mat, cam_pos) + self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w()); } - pub fn frustum(&self, client: &Client) -> Frustum { - let (view_mat, proj_mat, _) = self.compute_dependents(client); - - Frustum::from_modelview_projection((proj_mat * view_mat).into_col_arrays()) + pub fn frustum(&self) -> Frustum { + Frustum::from_modelview_projection( + (self.dependents.proj_mat * self.dependents.view_mat).into_col_arrays(), + ) } + pub fn dependents(&self) -> Dependents { self.dependents.clone() } + /// Rotate the camera about its focus by the given delta, limiting the input /// accordingly. pub fn rotate_by(&mut self, delta: Vec3) { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 9feaf4f7f1..47a0674246 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1,5 +1,5 @@ mod cache; -mod load; +pub mod load; pub use cache::FigureModelCache; pub use load::load_mesh; // TODO: Don't make this public. @@ -13,14 +13,17 @@ use crate::{ quadruped_small::QuadrupedSmallSkeleton, Animation, Skeleton, }, render::{Consts, FigureBoneData, FigureLocals, Globals, Light, Renderer, Shadow}, - scene::camera::{Camera, CameraMode}, + scene::{ + camera::{Camera, CameraMode}, + SceneData, + }, }; -use client::Client; use common::{ comp::{ ActionState::*, Body, CharacterState, ItemKind, Last, MovementState::*, Ori, Pos, Scale, Stats, Vel, }, + state::State, terrain::TerrainChunk, vol::RectRasterableVol, }; @@ -96,17 +99,18 @@ impl FigureMgr { self.biped_large_model_cache.clean(tick); } - pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client, camera: &Camera) { - let time = client.state().get_time(); - let tick = client.get_tick(); - let ecs = client.state().ecs(); - let view_distance = client.view_distance().unwrap_or(1); - let dt = client.state().get_delta_time(); - let frustum = camera.frustum(client); + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData, camera: &Camera) { + let state = scene_data.state; + let time = state.get_time(); + let tick = scene_data.tick; + let ecs = state.ecs(); + let view_distance = scene_data.view_distance; + let dt = state.get_delta_time(); + let frustum = camera.frustum(); // Get player position. let player_pos = ecs .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) .map_or(Vec3::zero(), |pos| pos.0); for (entity, pos, ori, scale, body, character, last_character, stats) in ( @@ -1181,7 +1185,7 @@ impl FigureMgr { } } - // Clear states that have dead entities. + // Clear states that have deleted entities. self.character_states .retain(|entity, _| ecs.entities().is_alive(*entity)); self.quadruped_small_states @@ -1209,19 +1213,18 @@ impl FigureMgr { pub fn render( &mut self, renderer: &mut Renderer, - client: &mut Client, + state: &State, + player_entity: EcsEntity, + tick: u64, globals: &Consts, lights: &Consts, shadows: &Consts, camera: &Camera, ) { - let tick = client.get_tick(); - let ecs = client.state().ecs(); + let ecs = state.ecs(); - let character_state_storage = client - .state() - .read_storage::(); - let character_state = character_state_storage.get(client.entity()); + let character_state_storage = state.read_storage::(); + let character_state = character_state_storage.get(player_entity); for (entity, _, _, body, stats, _) in ( &ecs.entities(), @@ -1235,7 +1238,7 @@ impl FigureMgr { // Don't render dead entities .filter(|(_, _, _, _, stats, _)| stats.map_or(true, |s| !s.is_dead)) { - let is_player = entity == client.entity(); + let is_player = entity == player_entity; let player_camera_mode = if is_player { camera.get_mode() } else { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 6653a75e8e..84c6aa2f3e 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,29 +1,29 @@ pub mod camera; pub mod figure; +pub mod simple; pub mod terrain; use self::{ camera::{Camera, CameraMode}, figure::FigureMgr, - music::MusicMgr, terrain::Terrain, }; use crate::{ anim::character::SkeletonAttr, - audio::{music, sfx::SfxMgr, AudioFrontend}, + audio::{music::MusicMgr, sfx::SfxMgr, AudioFrontend}, render::{ create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, }, window::Event, }; -use client::Client; use common::{ comp, + state::State, terrain::{BlockKind, TerrainChunk}, vol::ReadVol, }; -use specs::{Join, WorldExt}; +use specs::{Entity as EcsEntity, Join, WorldExt}; use vek::*; // TODO: Don't hard-code this. @@ -62,6 +62,15 @@ pub struct Scene { music_mgr: MusicMgr, } +pub struct SceneData<'a> { + pub state: &'a State, + pub player_entity: specs::Entity, + pub loaded_distance: f32, + pub view_distance: u32, + pub tick: u64, + pub thread_pool: &'a uvth::ThreadPool, +} + impl Scene { /// Create a new `Scene` with default parameters. pub fn new(renderer: &mut Renderer) -> Self { @@ -148,29 +157,29 @@ impl Scene { &mut self, renderer: &mut Renderer, audio: &mut AudioFrontend, - client: &Client, + scene_data: &SceneData, gamma: f32, ) { // Get player position. - let player_pos = client - .state() + let player_pos = scene_data + .state .ecs() .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) .map_or(Vec3::zero(), |pos| pos.0); - let player_rolling = client - .state() + let player_rolling = scene_data + .state .ecs() .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) .map_or(false, |cs| cs.action.is_roll()); - let player_scale = match client - .state() + let player_scale = match scene_data + .state .ecs() .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) { Some(comp::Body::Humanoid(body)) => SkeletonAttr::calculate_scale(body), _ => 1_f32, @@ -196,25 +205,30 @@ impl Scene { ); // Tick camera for interpolation. - self.camera.update(client.state().get_time()); + self.camera.update(scene_data.state.get_time()); // Compute camera matrices. - let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client); + self.camera.compute_dependents(&*scene_data.state.terrain()); + let camera::Dependents { + view_mat, + proj_mat, + cam_pos, + } = self.camera.dependents(); // Update chunk loaded distance smoothly for nice shader fog - let loaded_distance = client.loaded_distance(); - self.loaded_distance = (0.98 * self.loaded_distance + 0.02 * loaded_distance).max(0.01); + self.loaded_distance = + (0.98 * self.loaded_distance + 0.02 * scene_data.loaded_distance).max(0.01); // Update light constants let mut lights = ( - &client.state().ecs().read_storage::(), - client.state().ecs().read_storage::().maybe(), - client - .state() + &scene_data.state.ecs().read_storage::(), + scene_data.state.ecs().read_storage::().maybe(), + scene_data + .state .ecs() .read_storage::() .maybe(), - &client.state().ecs().read_storage::(), + &scene_data.state.ecs().read_storage::(), ) .join() .filter(|(pos, _, _, _)| { @@ -247,15 +261,15 @@ impl Scene { // Update shadow constants let mut shadows = ( - &client.state().ecs().read_storage::(), - client - .state() + &scene_data.state.ecs().read_storage::(), + scene_data + .state .ecs() .read_storage::() .maybe(), - client.state().ecs().read_storage::().maybe(), - &client.state().ecs().read_storage::(), - &client.state().ecs().read_storage::(), + scene_data.state.ecs().read_storage::().maybe(), + &scene_data.state.ecs().read_storage::(), + &scene_data.state.ecs().read_storage::(), ) .join() .filter(|(_, _, _, _, stats)| !stats.is_dead) @@ -285,13 +299,13 @@ impl Scene { cam_pos, self.camera.get_focus_pos(), self.loaded_distance, - client.state().get_time_of_day(), - client.state().get_time(), + scene_data.state.get_time_of_day(), + scene_data.state.get_time(), renderer.get_resolution(), lights.len(), shadows.len(), - client - .state() + scene_data + .state .terrain() .get(cam_pos.map(|e| e.floor() as i32)) .map(|b| b.kind()) @@ -304,7 +318,7 @@ impl Scene { // Maintain the terrain. self.terrain.maintain( renderer, - client, + &scene_data, self.camera.get_focus_pos(), self.loaded_distance, view_mat, @@ -312,22 +326,31 @@ impl Scene { ); // Maintain the figures. - self.figure_mgr.maintain(renderer, client, &self.camera); + self.figure_mgr.maintain(renderer, scene_data, &self.camera); // Remove unused figures. - self.figure_mgr.clean(client.get_tick()); + self.figure_mgr.clean(scene_data.tick); // Maintain audio - self.sfx_mgr.maintain(audio, client); - self.music_mgr.maintain(audio, client); + self.sfx_mgr + .maintain(audio, scene_data.state, scene_data.player_entity); + self.music_mgr.maintain(audio, scene_data.state); } /// Render the scene using the provided `Renderer`. - pub fn render(&mut self, renderer: &mut Renderer, client: &mut Client) { + pub fn render( + &mut self, + renderer: &mut Renderer, + state: &State, + player_entity: EcsEntity, + tick: u64, + ) { // Render terrain and figures. self.figure_mgr.render( renderer, - client, + state, + player_entity, + tick, &self.globals, &self.lights, &self.shadows, diff --git a/voxygen/src/menu/char_selection/scene.rs b/voxygen/src/scene/simple.rs similarity index 66% rename from voxygen/src/menu/char_selection/scene.rs rename to voxygen/src/scene/simple.rs index c09f794013..e319c44942 100644 --- a/voxygen/src/menu/char_selection/scene.rs +++ b/voxygen/src/scene/simple.rs @@ -9,21 +9,37 @@ use crate::{ PostProcessLocals, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, }, scene::{ - camera::{Camera, CameraMode}, + camera::{self, Camera, CameraMode}, figure::{load_mesh, FigureModelCache, FigureState}, }, window::{Event, PressState}, }; -use client::Client; use common::{ comp::{humanoid, Body, Equipment}, - state::DeltaTime, terrain::BlockKind, + vol::{BaseVol, ReadVol, Vox}, }; use log::error; -use specs::WorldExt; use vek::*; +#[derive(PartialEq, Eq, Copy, Clone)] +struct VoidVox; +impl Vox for VoidVox { + fn empty() -> Self { VoidVox } + + fn is_empty(&self) -> bool { true } + + fn or(self, _other: Self) -> Self { VoidVox } +} +struct VoidVol; +impl BaseVol for VoidVol { + type Error = (); + type Vox = VoidVox; +} +impl ReadVol for VoidVol { + fn get<'a>(&'a self, _pos: Vec3) -> Result<&'a Self::Vox, Self::Error> { Ok(&VoidVox) } +} + struct Skybox { model: Model, locals: Consts, @@ -42,8 +58,7 @@ pub struct Scene { skybox: Skybox, postprocess: PostProcess, - backdrop_model: Model, - backdrop_state: FigureState, + backdrop: Option<(Model, FigureState)>, figure_model_cache: FigureModelCache, figure_state: FigureState, @@ -52,15 +67,28 @@ pub struct Scene { char_ori: f32, } +pub struct SceneData { + pub time: f64, + pub delta_time: f32, + pub tick: u64, + pub body: Option, + pub gamma: f32, +} + impl Scene { - pub fn new(renderer: &mut Renderer) -> Self { + pub fn new(renderer: &mut Renderer, backdrop: Option<&str>) -> Self { let resolution = renderer.get_resolution().map(|e| e as f32); + let mut camera = Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson); + camera.set_focus_pos(Vec3::unit_z() * 1.5); + camera.set_distance(3.0); // 4.2 + camera.set_orientation(Vec3::new(0.0, 0.0, 0.0)); + Self { globals: renderer.create_consts(&[Globals::default()]).unwrap(), lights: renderer.create_consts(&[Light::default(); 32]).unwrap(), shadows: renderer.create_consts(&[Shadow::default(); 32]).unwrap(), - camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson), + camera, skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), @@ -75,13 +103,14 @@ impl Scene { figure_model_cache: FigureModelCache::new(), figure_state: FigureState::new(renderer, CharacterSkeleton::new()), - backdrop_model: renderer - .create_model(&load_mesh( - "fixture.selection_bg", - Vec3::new(-55.0, -49.5, -2.0), - )) - .unwrap(), - backdrop_state: FigureState::new(renderer, FixtureSkeleton::new()), + backdrop: backdrop.map(|specifier| { + ( + renderer + .create_model(&load_mesh(specifier, Vec3::new(-55.0, -49.5, -2.0))) + .unwrap(), + FigureState::new(renderer, FixtureSkeleton::new()), + ) + }), turning: false, char_ori: 0.0, @@ -90,6 +119,8 @@ impl Scene { pub fn globals(&self) -> &Consts { &self.globals } + pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera } + /// Handle an incoming user input event (e.g.: cursor moved, key pressed, /// window closed). /// @@ -114,20 +145,18 @@ impl Scene { } } - pub fn maintain( - &mut self, - renderer: &mut Renderer, - client: &Client, - body: Option, - gamma: f32, - ) { - self.camera.set_focus_pos(Vec3::unit_z() * 1.5); - self.camera.update(client.state().get_time()); - self.camera.set_distance(3.0); // 4.2 - self.camera - .set_orientation(Vec3::new(client.state().get_time() as f32 * 0.0, 0.0, 0.0)); + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: SceneData) { + self.camera.update(scene_data.time); - let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client); + //self.camera + // .set_orientation(Vec3::new(scene_data.time as f32 * 0.0, 0.0, 0.0)); + + self.camera.compute_dependents(&VoidVol); + let camera::Dependents { + view_mat, + proj_mat, + cam_pos, + } = self.camera.dependents(); const VD: f32 = 115.0; //View Distance const TIME: f64 = 43200.0; // hours*3600 seconds if let Err(err) = renderer.update_consts(&mut self.globals, &[Globals::new( @@ -137,31 +166,30 @@ impl Scene { self.camera.get_focus_pos(), VD, TIME, - client.state().get_time(), + scene_data.time, renderer.get_resolution(), 0, 0, BlockKind::Air, None, - gamma, + scene_data.gamma, )]) { error!("Renderer failed to update: {:?}", err); } - self.figure_model_cache.clean(client.get_tick()); + self.figure_model_cache.clean(scene_data.tick); - if let Some(body) = body { + if let Some(body) = scene_data.body { let tgt_skeleton = IdleAnimation::update_skeleton( self.figure_state.skeleton_mut(), - client.state().get_time(), - client.state().get_time(), + scene_data.time, + scene_data.time, &mut 0.0, &SkeletonAttr::from(&body), ); - self.figure_state.skeleton_mut().interpolate( - &tgt_skeleton, - client.state().ecs().read_resource::().0, - ); + self.figure_state + .skeleton_mut() + .interpolate(&tgt_skeleton, scene_data.delta_time); } self.figure_state.update( @@ -182,7 +210,7 @@ impl Scene { pub fn render( &mut self, renderer: &mut Renderer, - client: &Client, + tick: u64, body: Option, equipment: &Equipment, ) { @@ -195,7 +223,7 @@ impl Scene { renderer, Body::Humanoid(body), Some(equipment), - client.get_tick(), + tick, CameraMode::default(), None, ) @@ -211,14 +239,16 @@ impl Scene { ); } - renderer.render_figure( - &self.backdrop_model, - &self.globals, - self.backdrop_state.locals(), - self.backdrop_state.bone_consts(), - &self.lights, - &self.shadows, - ); + if let Some((model, state)) = &self.backdrop { + renderer.render_figure( + model, + &self.globals, + state.locals(), + state.bone_consts(), + &self.lights, + &self.shadows, + ); + } renderer.render_post_process( &self.postprocess.model, diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index c6c41f4928..be5f7c40c3 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -6,7 +6,7 @@ use crate::{ }, }; -use client::Client; +use super::SceneData; use common::{ assets, figure::Segment, @@ -1083,26 +1083,26 @@ impl Terrain { pub fn maintain( &mut self, renderer: &mut Renderer, - client: &Client, + scene_data: &SceneData, focus_pos: Vec3, loaded_distance: f32, view_mat: Mat4, proj_mat: Mat4, ) { - let current_tick = client.get_tick(); - let current_time = client.state().get_time(); + let current_tick = scene_data.tick; + let current_time = scene_data.state.get_time(); // Add any recently created or changed chunks to the list of chunks to be // meshed. - for (modified, pos) in client - .state() + for (modified, pos) in scene_data + .state .terrain_changes() .modified_chunks .iter() .map(|c| (true, c)) .chain( - client - .state() + scene_data + .state .terrain_changes() .new_chunks .iter() @@ -1121,8 +1121,8 @@ impl Terrain { let mut neighbours = true; for i in -1..2 { for j in -1..2 { - neighbours &= client - .state() + neighbours &= scene_data + .state .terrain() .get_key(pos + Vec2::new(i, j)) .is_some(); @@ -1143,20 +1143,20 @@ impl Terrain { // Add the chunks belonging to recently changed blocks to the list of chunks to // be meshed - for pos in client - .state() + for pos in scene_data + .state .terrain_changes() .modified_blocks .iter() .map(|(p, _)| *p) { - let chunk_pos = client.state().terrain().pos_key(pos); + let chunk_pos = scene_data.state.terrain().pos_key(pos); // Only mesh if this chunk has all its neighbors let mut neighbours = true; for i in -1..2 { for j in -1..2 { - neighbours &= client - .state() + neighbours &= scene_data + .state .terrain() .get_key(chunk_pos + Vec2::new(i, j)) .is_some(); @@ -1178,15 +1178,15 @@ impl Terrain { for x in -1..2 { for y in -1..2 { let neighbour_pos = pos + Vec3::new(x, y, 0); - let neighbour_chunk_pos = client.state().terrain().pos_key(neighbour_pos); + let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos); if neighbour_chunk_pos != chunk_pos { // Only remesh if this chunk has all its neighbors let mut neighbours = true; for i in -1..2 { for j in -1..2 { - neighbours &= client - .state() + neighbours &= scene_data + .state .terrain() .get_key(neighbour_chunk_pos + Vec2::new(i, j)) .is_some(); @@ -1205,7 +1205,7 @@ impl Terrain { } // Remove any models for chunks that have been recently removed. - for pos in &client.state().terrain_changes().removed_chunks { + for pos in &scene_data.state.terrain_changes().removed_chunks { self.chunks.remove(pos); self.mesh_todo.remove(pos); } @@ -1220,7 +1220,7 @@ impl Terrain { }) .min_by_key(|todo| todo.active_worker.unwrap_or(todo.started_tick)) { - if client.thread_pool().queued_jobs() > 0 { + if scene_data.thread_pool.queued_jobs() > 0 { break; } @@ -1239,7 +1239,7 @@ impl Terrain { // Copy out the chunk data we need to perform the meshing. We do this by taking // a sample of the terrain that includes both the chunk we want and // its neighbours. - let volume = match client.state().terrain().sample(aabr) { + let volume = match scene_data.state.terrain().sample(aabr) { Ok(sample) => sample, // Either this chunk or its neighbours doesn't yet exist, so we keep it in the // queue to be processed at a later date when we have its neighbours. @@ -1266,7 +1266,7 @@ impl Terrain { // Queue the worker thread. let started_tick = todo.started_tick; - client.thread_pool().execute(move || { + scene_data.thread_pool.execute(move || { let _ = send.send(mesh_worker( pos, (min_z as f32, max_z as f32), diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7670eb1b1e..cf47210669 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -4,7 +4,7 @@ use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, key_state::KeyState, render::Renderer, - scene::Scene, + scene::{camera, Scene, SceneData}, window::{Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; @@ -108,7 +108,11 @@ impl SessionState { renderer.clear(); // Render the screen using the global renderer - self.scene.render(renderer, &mut self.client.borrow_mut()); + { + let client = self.client.borrow(); + self.scene + .render(renderer, client.state(), client.entity(), client.get_tick()); + } // Draw the UI to the screen self.hud.render(renderer, self.scene.globals()); @@ -145,10 +149,12 @@ impl PlayState for SessionState { let mut current_client_state = self.client.borrow().get_client_state(); while let ClientState::Pending | ClientState::Character = current_client_state { // Compute camera data - let (view_mat, _, cam_pos) = self - .scene - .camera() - .compute_dependents(&self.client.borrow()); + self.scene + .camera_mut() + .compute_dependents(&*self.client.borrow().state().terrain()); + let camera::Dependents { + view_mat, cam_pos, .. + } = self.scene.camera().dependents(); let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); // Check to see whether we're aiming at anything @@ -396,6 +402,10 @@ impl PlayState for SessionState { // Maintain global state. global_state.maintain(clock.get_last_delta().as_secs_f32()); + // Recompute dependents just in case some input modified the camera + self.scene + .camera_mut() + .compute_dependents(&*self.client.borrow().state().terrain()); // Extract HUD events ensuring the client borrow gets dropped. let mut hud_events = self.hud.maintain( &self.client.borrow(), @@ -555,6 +565,9 @@ impl PlayState for SessionState { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn(); self.scene.camera_mut().set_fov_deg(new_fov); + self.scene + .camera_mut() + .compute_dependents(&*self.client.borrow().state().terrain()); }, HudEvent::ChangeGamma(new_gamma) => { global_state.settings.graphics.gamma = new_gamma; @@ -618,11 +631,19 @@ impl PlayState for SessionState { if global_state.singleplayer.is_none() || !global_state.singleplayer.as_ref().unwrap().is_paused() { - // Maintain the scene. + let client = self.client.borrow(); + let scene_data = SceneData { + state: client.state(), + player_entity: client.entity(), + loaded_distance: client.loaded_distance(), + view_distance: client.view_distance().unwrap_or(1), + tick: client.get_tick(), + thread_pool: client.thread_pool(), + }; self.scene.maintain( global_state.window.renderer_mut(), &mut global_state.audio, - &self.client.borrow(), + &scene_data, global_state.settings.graphics.gamma, ); } From 06759a6ed72558c5d6b4766f5fee9985db1c1afa Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 5 Mar 2020 19:07:48 -0500 Subject: [PATCH 21/22] Fix tests --- voxygen/src/scene/simple.rs | 7 ++----- voxygen/src/ui/img_ids.rs | 7 ++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index e319c44942..c12a6f9641 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -148,17 +148,14 @@ impl Scene { pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: SceneData) { self.camera.update(scene_data.time); - //self.camera - // .set_orientation(Vec3::new(scene_data.time as f32 * 0.0, 0.0, 0.0)); - self.camera.compute_dependents(&VoidVol); let camera::Dependents { view_mat, proj_mat, cam_pos, } = self.camera.dependents(); - const VD: f32 = 115.0; //View Distance - const TIME: f64 = 43200.0; // hours*3600 seconds + const VD: f32 = 115.0; // View Distance + const TIME: f64 = 43200.0; // 12 hours*3600 seconds if let Err(err) = renderer.update_consts(&mut self.globals, &[Globals::new( view_mat, proj_mat, diff --git a/voxygen/src/ui/img_ids.rs b/voxygen/src/ui/img_ids.rs index f3cfc0f65f..ea5a8ac294 100644 --- a/voxygen/src/ui/img_ids.rs +++ b/voxygen/src/ui/img_ids.rs @@ -114,7 +114,12 @@ pub struct Rotations { /// corresponding ImgIds and create a struct with all of them. /// /// Example usage: -/// ``` +/// ```ignore +/// use veloren_voxygen::{ +/// image_ids, +/// ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic}, +/// }; +/// /// image_ids! { /// pub struct Imgs { /// From 05f3b7f253f47be3d4bb33bd058ac98358e929f5 Mon Sep 17 00:00:00 2001 From: Songtronix Date: Mon, 30 Dec 2019 14:01:37 +0100 Subject: [PATCH 22/22] add: CODEOWNERS file introduces the need of approval by a specific group/person for the MR to be merged based on which part of the codebase is being touched --- .gitlab/CODEOWNERS | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .gitlab/CODEOWNERS diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS new file mode 100644 index 0000000000..c1aa0c58d8 --- /dev/null +++ b/.gitlab/CODEOWNERS @@ -0,0 +1,28 @@ +# Defines people who should approve to certain parts of the codebase + +/assets/ @assetsandvisualdesign @frontend +/chat-cli/ @frontend +/client/ @backend @networking +/common/ @backend @networking +/server/ @backend @networking +/server-cli/ @frontend +#/voxygen/ @someone +/voxygen/anim/ @animation +/voxygen/audio/ @audio +#/voxygen/hud/ @someone +#/voxygen/menu / @someone +#/voxygen/mesh/ @someone +/voxygen/render/ @rendering +#/voxygen/scene/ @someone +#/voxygen/ui/ @someone +/world/ @worldgen + +# All files related to documentation or game unrelated content needs to be approved by the meta group +*.md @meta +*.nix @meta +.gitignore @meta +.gitattributes @meta +.gitlab-ci.yml @meta +rust-toolchain @meta +LICENSE @meta +.cargo/ @meta \ No newline at end of file