Merge branch 'lboklin/gliding-improvements' into 'master'

Minor improvements to gliding and glider-wielding

See merge request veloren/veloren!2734
This commit is contained in:
Samuel Keiffer 2021-08-09 14:33:02 +00:00
commit 2bc4f364bf
21 changed files with 155 additions and 118 deletions

View File

@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- NPCs can now warn players before engaging in combat
- Custom error message when a supported graphics backend can not be found
- Add server setting with PvE/PvP switch
- Can now tilt glider while only wielding it
### Changed
@ -47,6 +48,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The types of animals that can be tamed as pets are now limited to certain species, pending further balancing of pets
- Made server-cli admin add/remove command use positional arguments again
- Usage of "stamina" replaced with "energy"
- Glider dimensions now depend on character height
- Glider dimensions somewhat increased overall
### Removed
@ -67,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Map will now zoom around the cursor's position and drag correctly
- No more jittering while running down slopes with the glider out
- Axe normal attack rewards energy without skill points
- Gliders no longer suffer from unreasonable amounts of induced drag
## [0.10.0] - 2021-06-12

View File

@ -4,7 +4,7 @@ BasicRanged(
recover_duration: 0.5,
projectile: ClayRocket(
damage: 500.0,
knockback: 25.0,
knockback: 18.0,
radius: 5.0,
),
projectile_body: Object(ClayRocket),

View File

@ -5,7 +5,7 @@ Shockwave(
recover_duration: 1.2,
damage: 500,
poise_damage: 50,
knockback: (strength: 40.0, direction: TowardsUp),
knockback: (strength: 30.0, direction: TowardsUp),
shockwave_angle: 180.0,
shockwave_vertical_angle: 90.0,
shockwave_speed: 15.0,

View File

@ -1,8 +1,8 @@
SpinMelee(
buildup_duration: 0.8,
swing_duration: 0.2,
swing_duration: 0.5,
recover_duration: 0.6,
base_damage: 80.0,
base_damage: 200.0,
base_poise_damage: 1.0,
knockback: ( strength: 7.0, direction: Towards),
range: 16.0,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.6,
base_damage: 50.0,
base_poise_damage: 0.0,
knockback: ( strength: 100.0, direction: Towards),
knockback: ( strength: 50.0, direction: Towards),
range: 5.0,
max_angle: 60.0,
damage_effect: None,

View File

@ -5,7 +5,7 @@ Shockwave(
recover_duration: 3.5,
damage: 10,
poise_damage: 0,
knockback: ( strength: 25.0, direction: Away),
knockback: ( strength: 18.0, direction: Away),
shockwave_angle: 360.0,
shockwave_vertical_angle: 30.0,
shockwave_speed: 10.0,

View File

@ -5,7 +5,7 @@ Shockwave(
recover_duration: 1.0,
damage: 100,
poise_damage: 10,
knockback: (strength: 25.0, direction: Up),
knockback: (strength: 18.0, direction: Up),
shockwave_angle: 90.0,
shockwave_vertical_angle: 15.0,
shockwave_speed: 15.0,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 1.0,
base_damage: 100,
base_poise_damage: 50,
knockback: ( strength: 70.0, direction: Away),
knockback: ( strength: 50.0, direction: Away),
range: 4.0,
max_angle: 20.0,
damage_effect: None,

View File

@ -119,6 +119,7 @@
(Sceptre(LRegen), 2),
(Sceptre(HHeal), 3),
(Sceptre(HDuration), 2),
(Sceptre(HCost), 2),
(Sceptre(HRange), 2),

View File

@ -210,14 +210,10 @@ impl Body {
//
// If you want to change that value, consult with
// Physics and Combat teams
match humanoid.species {
humanoid::Species::Orc => 100.0,
humanoid::Species::Elf => 85.0,
humanoid::Species::Human => 85.0,
humanoid::Species::Dwarf => 80.0,
humanoid::Species::Undead => 75.0,
humanoid::Species::Danari => 75.0,
}
//
// Weight is proportional height, where
// a 1.75m character would weigh 65kg
65.0 * humanoid.height() / 1.75f32
},
Body::Object(obj) => obj.mass().0,
Body::QuadrupedLow(body) => match body.species {
@ -331,21 +327,8 @@ impl Body {
Body::FishSmall(_) => Vec3::new(0.3, 1.2, 0.6),
Body::Golem(_) => Vec3::new(5.0, 5.0, 7.5),
Body::Humanoid(humanoid) => {
let height = match (humanoid.species, humanoid.body_type) {
(humanoid::Species::Orc, humanoid::BodyType::Male) => 2.0,
(humanoid::Species::Orc, humanoid::BodyType::Female) => 1.9,
(humanoid::Species::Human, humanoid::BodyType::Male) => 1.8,
(humanoid::Species::Human, humanoid::BodyType::Female) => 1.7,
(humanoid::Species::Elf, humanoid::BodyType::Male) => 1.9,
(humanoid::Species::Elf, humanoid::BodyType::Female) => 1.8,
(humanoid::Species::Dwarf, humanoid::BodyType::Male) => 1.6,
(humanoid::Species::Dwarf, humanoid::BodyType::Female) => 1.5,
(humanoid::Species::Undead, humanoid::BodyType::Male) => 1.9,
(humanoid::Species::Undead, humanoid::BodyType::Female) => 1.8,
(humanoid::Species::Danari, humanoid::BodyType::Male) => 1.5,
(humanoid::Species::Danari, humanoid::BodyType::Female) => 1.4,
};
Vec3::new(1.5, 0.5, height)
let height = humanoid.height();
Vec3::new(height / 1.3, 1.75 / 2.0, height)
},
Body::Object(object) => object.dimensions(),
Body::QuadrupedMedium(body) => match body.species {

View File

@ -64,6 +64,25 @@ impl Body {
.accessory
.min(self.species.num_accessories(self.body_type) - 1);
}
pub fn height(&self) -> f32 { (20.0 / 9.0) * self.scaler() }
pub fn scaler(&self) -> f32 {
match (self.species, self.body_type) {
(Species::Orc, BodyType::Male) => 0.91,
(Species::Orc, BodyType::Female) => 0.81,
(Species::Human, BodyType::Male) => 0.81,
(Species::Human, BodyType::Female) => 0.76,
(Species::Elf, BodyType::Male) => 0.82,
(Species::Elf, BodyType::Female) => 0.76,
(Species::Dwarf, BodyType::Male) => 0.67,
(Species::Dwarf, BodyType::Female) => 0.62,
(Species::Undead, BodyType::Male) => 0.78,
(Species::Undead, BodyType::Female) => 0.72,
(Species::Danari, BodyType::Male) => 0.56,
(Species::Danari, BodyType::Female) => 0.56,
}
}
}
impl From<Body> for super::Body {

View File

@ -85,6 +85,7 @@ impl BuffKind {
| BuffKind::Saturation
| BuffKind::Potion
| BuffKind::CampfireHeal
| BuffKind::Frenzied
| BuffKind::IncreaseMaxEnergy
| BuffKind::IncreaseMaxHealth
| BuffKind::Invulnerability
@ -93,7 +94,6 @@ impl BuffKind {
| BuffKind::Cursed
| BuffKind::Burning
| BuffKind::Crippled
| BuffKind::Frenzied
| BuffKind::Frozen
| BuffKind::Wet
| BuffKind::Ensnared => false,

View File

@ -140,7 +140,6 @@ impl Body {
Vec3::zero()
} else {
let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt());
// All the coefficients come pre-multiplied by their reference area
0.5 * fluid_density
* v_sq
* match wings {
@ -163,7 +162,7 @@ impl Body {
let aoa = angle_of_attack(&ori, &rel_flow_dir);
// c_l will be positive when aoa is positive (we have positive lift,
// producing an upward force) and negative otherwise
let c_l = lift_coefficient(ar, planform_area, aoa);
let c_l = lift_coefficient(ar, aoa);
// lift dir will be orthogonal to the local relative flow vector.
// Local relative flow is the resulting vector of (relative) freestream
@ -187,25 +186,24 @@ impl Body {
ori.pitched_down(aoa_eff).up()
};
// drag coefficient
let c_d = {
// induced drag coefficient (drag due to lift)
let cdi = {
// Oswald's efficiency factor (empirically derived--very magical)
// (this definition should not be used for aspect ratios > 25)
let e = 1.78 * (1.0 - 0.045 * ar.powf(0.68)) - 0.64;
// induced drag coefficient (drag due to lift)
let cdi = c_l.powi(2) / (PI * e * ar);
zero_lift_drag_coefficient(planform_area)
+ self.parasite_drag_coefficient()
+ cdi
c_l.powi(2) / (PI * e * ar)
};
// drag coefficient
let c_d = zero_lift_drag_coefficient() + cdi;
debug_assert!(c_d.is_sign_positive());
debug_assert!(c_l.is_sign_positive() || aoa.is_sign_negative());
c_l * *lift_dir + c_d * *rel_flow_dir
planform_area * (c_l * *lift_dir + c_d * *rel_flow_dir)
+ self.parasite_drag() * *rel_flow_dir
},
_ => self.parasite_drag_coefficient() * *rel_flow_dir,
_ => self.parasite_drag() * *rel_flow_dir,
}
}
}
@ -214,7 +212,7 @@ impl Body {
/// Skin friction is the drag arising from the shear forces between a fluid
/// and a surface, while pressure drag is due to flow separation. Both are
/// viscous effects.
fn parasite_drag_coefficient(&self) -> f32 {
fn parasite_drag(&self) -> f32 {
// Reference area and drag coefficient assumes best-case scenario of the
// orientation producing least amount of drag
match self {
@ -330,33 +328,32 @@ pub fn angle_of_attack(ori: &Ori, rel_flow_dir: &Dir) -> f32 {
/// Total lift coefficient for a finite wing of symmetric aerofoil shape and
/// elliptical pressure distribution.
pub fn lift_coefficient(aspect_ratio: f32, planform_area: f32, aoa: f32) -> f32 {
pub fn lift_coefficient(aspect_ratio: f32, aoa: f32) -> f32 {
let aoa_abs = aoa.abs();
let stall_angle = PI * 0.1;
planform_area
* if aoa_abs < stall_angle {
lift_slope(aspect_ratio, None) * aoa
if aoa_abs < stall_angle {
lift_slope(aspect_ratio, None) * aoa
} else {
// This is when flow separation and turbulence starts to kick in.
// Going to just make something up (based on some data), as the alternative is
// to just throw your hands up and return 0
let aoa_s = aoa.signum();
let c_l_max = lift_slope(aspect_ratio, None) * stall_angle;
let deg_45 = PI / 4.0;
if aoa_abs < deg_45 {
// drop directly to 0.6 * max lift at stall angle
// then climb back to max at 45°
Lerp::lerp(0.6 * c_l_max, c_l_max, aoa_abs / deg_45) * aoa_s
} else {
// This is when flow separation and turbulence starts to kick in.
// Going to just make something up (based on some data), as the alternative is
// to just throw your hands up and return 0
let aoa_s = aoa.signum();
let c_l_max = lift_slope(aspect_ratio, None) * stall_angle;
let deg_45 = PI / 4.0;
if aoa_abs < deg_45 {
// drop directly to 0.6 * max lift at stall angle
// then climb back to max at 45°
Lerp::lerp(0.6 * c_l_max, c_l_max, aoa_abs / deg_45) * aoa_s
} else {
// let's just say lift goes down linearly again until we're at 90°
Lerp::lerp(c_l_max, 0.0, (aoa_abs - deg_45) / deg_45) * aoa_s
}
// let's just say lift goes down linearly again until we're at 90°
Lerp::lerp(c_l_max, 0.0, (aoa_abs - deg_45) / deg_45) * aoa_s
}
}
}
/// The zero-lift profile drag coefficient is the parasite drag on the wings
/// at the angle of attack which generates no lift
pub fn zero_lift_drag_coefficient(planform_area: f32) -> f32 { planform_area * 0.004 }
pub fn zero_lift_drag_coefficient() -> f32 { 0.026 }
/// The change in lift over change in angle of attack¹. Multiplying by angle
/// of attack gives the lift coefficient (for a finite wing, not aerofoil).

View File

@ -194,6 +194,30 @@ impl Ori {
self.rotated(Quaternion::rotation_y(angle_radians))
}
/// Returns a version which is rolled such that its up points towards `dir`
/// as much as possible without pitching or yawing
pub fn rolled_towards(self, dir: Dir) -> Self {
dir.projected(&Plane::from(self.look_dir()))
.map_or(self, |dir| self.prerotated(self.up().rotation_between(dir)))
}
/// Returns a version which has been pitched towards `dir` as much as
/// possible without yawing or rolling
pub fn pitched_towards(self, dir: Dir) -> Self {
dir.projected(&Plane::from(self.right()))
.map_or(self, |dir_| {
self.prerotated(self.look_dir().rotation_between(dir_))
})
}
/// Returns a version which has been yawed towards `dir` as much as possible
/// without pitching or rolling
pub fn yawed_towards(self, dir: Dir) -> Self {
dir.projected(&Plane::from(self.up())).map_or(self, |dir_| {
self.prerotated(self.look_dir().rotation_between(dir_))
})
}
/// Returns a version without sideways tilt (roll)
///
/// ```

View File

@ -576,7 +576,7 @@ pub enum SwimSkill {
impl Boost for SwimSkill {
fn boost(self) -> BoostValue {
match self {
Self::Speed => 40.into(),
Self::Speed => 25.into(),
}
}
}

View File

@ -80,7 +80,7 @@ impl CharacterBehavior for Data {
if data.physics.on_ground.is_some()
&& (data.vel.0 - data.physics.ground_vel).magnitude_squared() < 2_f32.powi(2)
{
update.character = CharacterState::GlideWield(glide_wield::Data::default());
update.character = CharacterState::GlideWield(glide_wield::Data::from(data));
} else if data.physics.in_liquid().is_some()
|| data
.inventory

View File

@ -1,6 +1,6 @@
use super::utils::*;
use crate::{
comp::{slot::EquipSlot, CharacterState, InventoryAction, StateUpdate},
comp::{slot::EquipSlot, CharacterState, InventoryAction, Ori, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
glide,
@ -10,13 +10,25 @@ use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
// time since left ground, for preventing state transition
// before block snapping kicks in
timer: f32,
pub ori: Ori,
span_length: f32,
chord_length: f32,
}
impl Default for Data {
fn default() -> Self { Self { timer: 0.0 } }
impl From<&JoinData<'_>> for Data {
fn from(data: &JoinData) -> Self {
let scale = data.body.dimensions().z.sqrt();
Self {
// Aspect ratio is what really matters for lift/drag ratio
// and the aerodynamics model works for ARs up to 25.
// The inflated dimensions are hopefully only a temporary
// bandaid for the poor glide ratio experienced under 2.5G.
// A span/chord ratio of 4.5 gives an AR of ~5.73.
span_length: scale * 4.5,
chord_length: scale,
ori: *data.ori,
}
}
}
impl CharacterBehavior for Data {
@ -29,31 +41,37 @@ impl CharacterBehavior for Data {
handle_dodge_input(data, &mut update);
handle_wield(data, &mut update);
// If not on the ground while wielding glider enter gliding state
if data.physics.on_ground.is_none() {
update.character = if self.timer > 0.3 {
CharacterState::Glide(glide::Data::new(10.0, 0.6, *data.ori))
} else {
// If still in this state, do the things
if matches!(update.character, CharacterState::GlideWield(_)) {
// If not on the ground while wielding glider enter gliding state
update.character = if data.physics.on_ground.is_none() {
CharacterState::Glide(glide::Data::new(
self.span_length,
self.chord_length,
self.ori,
))
// make sure we have a glider and we're not (too deep) in water
} else if data
.inventory
.and_then(|inv| inv.equipped(EquipSlot::Glider))
.is_some()
&& data.physics.in_liquid().map_or(true, |depth| depth < 0.5)
{
CharacterState::GlideWield(Self {
timer: self.timer + data.dt.0,
// Glider tilt follows look dir
ori: self.ori.slerped_towards(
data.ori.slerped_towards(
Ori::from(data.inputs.look_dir).pitched_up(0.6),
(1.0 + data.inputs.look_dir.dot(*data.ori.look_dir()).max(0.0)) / 3.0,
),
5.0 * data.dt.0,
),
..*self
})
} else {
CharacterState::Idle
};
}
if data
.physics
.in_liquid()
.map(|depth| depth > 0.5)
.unwrap_or(false)
{
update.character = CharacterState::Idle;
}
if data
.inventory
.and_then(|inv| inv.equipped(EquipSlot::Glider))
.is_none()
{
update.character = CharacterState::Idle
};
update
}

View File

@ -184,7 +184,7 @@ impl Body {
Body::FishMedium(_) => Some(50.0 * self.mass().0),
Body::FishSmall(_) => Some(50.0 * self.mass().0),
Body::Dragon(_) => Some(200.0 * self.mass().0),
Body::Humanoid(_) => Some(200.0 * self.mass().0),
Body::Humanoid(_) => Some(2500.0 * self.mass().0),
Body::Theropod(body) => match body.species {
theropod::Species::Sandraptor
| theropod::Species::Snowraptor
@ -375,7 +375,7 @@ fn swim_move(
let mut water_accel = force / data.mass.0;
if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
water_accel *= 1.4_f32.powi(level.into());
water_accel *= 1.25_f32.powi(level.into());
}
let dir = if data.body.can_strafe() {
@ -638,7 +638,7 @@ pub fn attempt_glide_wield(data: &JoinData<'_>, update: &mut StateUpdate) {
.unwrap_or(false)
&& data.body.is_humanoid()
{
update.character = CharacterState::GlideWield(glide_wield::Data::default());
update.character = CharacterState::GlideWield(glide_wield::Data::from(data));
}
}

View File

@ -5,8 +5,9 @@ use super::{
pub struct GlideWieldAnimation;
type GlideWieldAnimationDependency = (Quaternion<f32>, Quaternion<f32>);
impl Animation for GlideWieldAnimation {
type Dependency<'a> = ();
type Dependency<'a> = GlideWieldAnimationDependency;
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
@ -16,21 +17,25 @@ impl Animation for GlideWieldAnimation {
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
_: Self::Dependency<'a>,
(orientation, glider_orientation): Self::Dependency<'a>,
_anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let glider_ori = orientation.inverse() * glider_orientation;
let glider_pos = Vec3::new(0.0, -5.0, 13.0);
*rate = 1.0;
next.hand_l.position = Vec3::new(-2.0 - s_a.hand.0, s_a.hand.1, s_a.hand.2 + 15.0);
next.hand_l.position =
glider_pos + glider_ori * Vec3::new(-s_a.hand.0 + -2.0, s_a.hand.1 + 8.0, s_a.hand.2);
next.hand_l.orientation = Quaternion::rotation_x(3.35) * Quaternion::rotation_y(0.2);
next.hand_r.position = Vec3::new(2.0 + s_a.hand.0, s_a.hand.1, s_a.hand.2 + 15.0);
next.hand_r.position =
glider_pos + glider_ori * Vec3::new(s_a.hand.0 + 2.0, s_a.hand.1 + 8.0, s_a.hand.2);
next.hand_r.orientation = Quaternion::rotation_x(3.35) * Quaternion::rotation_y(-0.2);
next.glider.scale = Vec3::one() * 1.0;
next.glider.orientation = Quaternion::rotation_x(0.35);
next.glider.orientation = glider_ori;
next.glider.position = Vec3::new(0.0, -5.0, 13.0);

View File

@ -231,21 +231,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
fn from(body: &'a Body) -> Self {
use comp::humanoid::{BodyType::*, Species::*};
Self {
scaler: match (body.species, body.body_type) {
// TODO : Derive scale from body proportions
(Orc, Male) => 0.91,
(Orc, Female) => 0.81,
(Human, Male) => 0.81,
(Human, Female) => 0.76,
(Elf, Male) => 0.82,
(Elf, Female) => 0.76,
(Dwarf, Male) => 0.67,
(Dwarf, Female) => 0.62,
(Undead, Male) => 0.78,
(Undead, Female) => 0.72,
(Danari, Male) => 0.56,
(Danari, Female) => 0.56,
},
scaler: body.scaler(),
head_scale: match (body.species, body.body_type) {
(Orc, Male) => 0.9,
(Orc, Female) => 0.9,

View File

@ -1572,10 +1572,10 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::GlideWield { .. } => {
CharacterState::GlideWield(data) => {
anim::character::GlideWieldAnimation::update_skeleton(
&target_base,
(),
(ori, data.ori.into()),
state.state_time,
&mut state_animation_rate,
skeleton_attr,