diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f7336fbb8..e43717338f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sort inventory button - Option to change the master volume when window is unfocused - Crafting stations in towns - +- Option to change the master volume when window is unfocused +- Entities now have mass +- Entities now have density +- Buoyancy is calculated from the difference in density between an entity and surrounding fluid +- Drag is now calculated based on physical properties ### Changed @@ -64,10 +68,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Certain uses of client-authoritative physics now subject the player to server-authoritative physics. - Dodge roll iframes and staff explosion are now unlocked by default, with points refunded for existing characters. - Dash melee now stops after hitting something. Infinite dash also now replaced with dash through. +- Collisions, knockbacks, jumping and drag are now physical forces applied to the entity's body mass +- Turning rate has been made more consistent across angles +- Gravity has been lowered so that physics can work more reasonably +- Jump has been decreased in height but extended in length as a result of the new gravity +- Fall damage has been adjusted with the new gravity in mind +- Projectiles now generally have a different arc because they no longer have their own gravity modifier ### Removed - Removed command: "debug", use "/kit debug" instead +- Gravity component has been removed +- In-air movement has been removed ### Fixed diff --git a/assets/common/abilities/axe/leap.ron b/assets/common/abilities/axe/leap.ron index c59b733465..c86c081483 100644 --- a/assets/common/abilities/axe/leap.ron +++ b/assets/common/abilities/axe/leap.ron @@ -9,6 +9,6 @@ LeapMelee( knockback: 12.0, range: 4.5, max_angle: 30.0, - forward_leap_strength: 28.0, + forward_leap_strength: 20.0, vertical_leap_strength: 8.0, ) diff --git a/assets/common/abilities/bow/basic.ron b/assets/common/abilities/bow/basic.ron index fd47e9c51a..6fdc1a9087 100644 --- a/assets/common/abilities/bow/basic.ron +++ b/assets/common/abilities/bow/basic.ron @@ -9,6 +9,5 @@ BasicRanged( ), projectile_body: Object(Arrow), projectile_light: None, - projectile_gravity: Some(Gravity(0.2)), - projectile_speed: 100.0, + projectile_speed: 60.0, ) diff --git a/assets/common/abilities/bow/charged.ron b/assets/common/abilities/bow/charged.ron index 2bf5b53b7d..aa2080f578 100644 --- a/assets/common/abilities/bow/charged.ron +++ b/assets/common/abilities/bow/charged.ron @@ -11,8 +11,7 @@ ChargedRanged( recover_duration: 0.5, projectile_body: Object(MultiArrow), projectile_light: None, - projectile_gravity: Some(Gravity(0.2)), - initial_projectile_speed: 100.0, - scaled_projectile_speed: 400.0, + initial_projectile_speed: 60.0, + scaled_projectile_speed: 90.0, move_speed: 0.3, ) diff --git a/assets/common/abilities/bow/repeater.ron b/assets/common/abilities/bow/repeater.ron index 2f691189bf..e414240342 100644 --- a/assets/common/abilities/bow/repeater.ron +++ b/assets/common/abilities/bow/repeater.ron @@ -12,7 +12,6 @@ RepeaterRanged( ), projectile_body: Object(Arrow), projectile_light: None, - projectile_gravity: Some(Gravity(0.2)), - projectile_speed: 100.0, + projectile_speed: 60.0, reps_remaining: 3, ) diff --git a/assets/common/abilities/bowsimple/basic.ron b/assets/common/abilities/bowsimple/basic.ron index fd47e9c51a..84534ca1df 100644 --- a/assets/common/abilities/bowsimple/basic.ron +++ b/assets/common/abilities/bowsimple/basic.ron @@ -9,6 +9,5 @@ BasicRanged( ), projectile_body: Object(Arrow), projectile_light: None, - projectile_gravity: Some(Gravity(0.2)), projectile_speed: 100.0, ) diff --git a/assets/common/abilities/debug/possess.ron b/assets/common/abilities/debug/possess.ron index 7554d1fa65..e36ab3a45e 100644 --- a/assets/common/abilities/debug/possess.ron +++ b/assets/common/abilities/debug/possess.ron @@ -8,6 +8,5 @@ BasicRanged( col: (0.0, 1.0, 0.33).into(), ..Default::default() }),*/ - projectile_gravity: None, projectile_speed: 100.0, ) \ No newline at end of file diff --git a/assets/common/abilities/hammer/leap.ron b/assets/common/abilities/hammer/leap.ron index 47f90adb77..a7e8f51bc8 100644 --- a/assets/common/abilities/hammer/leap.ron +++ b/assets/common/abilities/hammer/leap.ron @@ -9,6 +9,6 @@ LeapMelee( knockback: 25.0, range: 4.5, max_angle: 360.0, - forward_leap_strength: 24.0, + forward_leap_strength: 20.0, vertical_leap_strength: 8.0, ) diff --git a/assets/common/abilities/hammer/singlestrike.ron b/assets/common/abilities/hammer/singlestrike.ron index bca59e0a90..e6bc548538 100644 --- a/assets/common/abilities/hammer/singlestrike.ron +++ b/assets/common/abilities/hammer/singlestrike.ron @@ -5,7 +5,7 @@ ComboMelee( damage_increase: 10, base_poise_damage: 25, poise_damage_increase: 0, - knockback: 10.0, + knockback: 5.0, range: 4.5, angle: 50.0, base_buildup_duration: 0.2, diff --git a/assets/common/abilities/staff/firebomb.ron b/assets/common/abilities/staff/firebomb.ron index 06114b4660..32e3d23eff 100644 --- a/assets/common/abilities/staff/firebomb.ron +++ b/assets/common/abilities/staff/firebomb.ron @@ -12,6 +12,5 @@ BasicRanged( col: (1.0, 0.75, 0.11).into(), ..Default::default() }),*/ - projectile_gravity: Some(Gravity(0.3)), projectile_speed: 60.0, ) diff --git a/assets/common/abilities/staffsimple/firebomb.ron b/assets/common/abilities/staffsimple/firebomb.ron index 48a5827527..ced62b3e95 100644 --- a/assets/common/abilities/staffsimple/firebomb.ron +++ b/assets/common/abilities/staffsimple/firebomb.ron @@ -12,6 +12,5 @@ BasicRanged( col: (1.0, 0.75, 0.11).into(), ..Default::default() }),*/ - projectile_gravity: Some(Gravity(0.3)), projectile_speed: 60.0, ) diff --git a/assets/common/abilities/sword/triplestrike.ron b/assets/common/abilities/sword/triplestrike.ron index 6e52582f56..a9a9683a9f 100644 --- a/assets/common/abilities/sword/triplestrike.ron +++ b/assets/common/abilities/sword/triplestrike.ron @@ -6,7 +6,7 @@ ComboMelee( damage_increase: 10, base_poise_damage: 10, poise_damage_increase: 0, - knockback: 10.0, + knockback: 1.0, range: 4.0, angle: 30.0, base_buildup_duration: 0.15, @@ -20,7 +20,7 @@ ComboMelee( damage_increase: 15, base_poise_damage: 13, poise_damage_increase: 0, - knockback: 12.0, + knockback: 2.0, range: 3.5, angle: 40.0, base_buildup_duration: 0.1, @@ -34,7 +34,7 @@ ComboMelee( damage_increase: 20, base_poise_damage: 15, poise_damage_increase: 0, - knockback: 14.0, + knockback: 4.0, range: 6.0, angle: 10.0, base_buildup_duration: 0.15, diff --git a/assets/common/abilities/unique/quadlowranged/firebomb.ron b/assets/common/abilities/unique/quadlowranged/firebomb.ron index c9661c5d0b..5b3917892a 100644 --- a/assets/common/abilities/unique/quadlowranged/firebomb.ron +++ b/assets/common/abilities/unique/quadlowranged/firebomb.ron @@ -12,6 +12,5 @@ BasicRanged( col: (1.0, 0.75, 0.11).into(), ..Default::default() }),*/ - projectile_gravity: Some(Gravity(5.0)), projectile_speed: 70.0, ) diff --git a/assets/common/abilities/unique/turret/arrows.ron b/assets/common/abilities/unique/turret/arrows.ron index 04845845d1..edb9722851 100644 --- a/assets/common/abilities/unique/turret/arrows.ron +++ b/assets/common/abilities/unique/turret/arrows.ron @@ -9,6 +9,5 @@ BasicRanged( ), projectile_body: Object(ArrowTurret), projectile_light: None, - projectile_gravity: Some(Gravity(0.1)), - projectile_speed: 100.0, + projectile_speed: 90.0, ) diff --git a/assets/common/abilities/unique/wendigomagic/frostbomb.ron b/assets/common/abilities/unique/wendigomagic/frostbomb.ron index b09592ebca..b6529121cf 100644 --- a/assets/common/abilities/unique/wendigomagic/frostbomb.ron +++ b/assets/common/abilities/unique/wendigomagic/frostbomb.ron @@ -11,6 +11,5 @@ BasicRanged( col: (1.0, 0.75, 0.11).into(), ..Default::default() }),*/ - projectile_gravity: Some(Gravity(0.3)), projectile_speed: 60.0, ) diff --git a/common/Cargo.toml b/common/Cargo.toml index 7e770dd2f7..8c3ef5d6c9 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -15,7 +15,7 @@ default = ["simd"] [dependencies] common-base = { package = "veloren-common-base", path = "base" } -#inline_tweak = "1.0.2" +# inline_tweak = "1.0.8" # Serde serde = { version = "1.0.110", features = ["derive", "rc"] } diff --git a/common/net/src/msg/ecs_packet.rs b/common/net/src/msg/ecs_packet.rs index 3828b11247..3dbe51e384 100644 --- a/common/net/src/msg/ecs_packet.rs +++ b/common/net/src/msg/ecs_packet.rs @@ -29,8 +29,8 @@ sum_type! { MountState(comp::MountState), Mounting(comp::Mounting), Mass(comp::Mass), + Density(comp::Density), Collider(comp::Collider), - Gravity(comp::Gravity), Sticky(comp::Sticky), CharacterState(comp::CharacterState), Pos(comp::Pos), @@ -64,8 +64,8 @@ sum_type! { MountState(PhantomData), Mounting(PhantomData), Mass(PhantomData), + Density(PhantomData), Collider(PhantomData), - Gravity(PhantomData), Sticky(PhantomData), CharacterState(PhantomData), Pos(PhantomData), @@ -99,8 +99,8 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::Density(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Collider(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Pos(comp) => { @@ -138,8 +138,8 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::Density(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Collider(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Pos(comp) => { @@ -179,8 +179,8 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::MountState(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Mounting(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Mass(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Density(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Collider(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Gravity(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Sticky(_) => sync::handle_remove::(entity, world), EcsCompPhantom::CharacterState(_) => { sync::handle_remove::(entity, world) diff --git a/common/src/combat.rs b/common/src/combat.rs index 6486a40a47..e2de8da7db 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -631,7 +631,8 @@ pub enum KnockbackDir { #[cfg(not(target_arch = "wasm32"))] impl Knockback { pub fn calculate_impulse(self, dir: Dir) -> Vec3 { - match self.direction { + // TEMP until source knockback values have been updated + 50.0 * match self.direction { KnockbackDir::Away => self.strength * *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5), KnockbackDir::Towards => { self.strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5) diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 42273034bc..e29b21021d 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -3,7 +3,7 @@ use crate::{ combat::{self, CombatEffect, Knockback}, comp::{ aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, - Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate, + Body, CharacterState, EnergySource, LightEmitter, StateUpdate, }, states::{ behavior::JoinData, @@ -74,7 +74,6 @@ pub enum CharacterAbility { projectile: ProjectileConstructor, projectile_body: Body, projectile_light: Option, - projectile_gravity: Option, projectile_speed: f32, }, RepeaterRanged { @@ -87,7 +86,6 @@ pub enum CharacterAbility { projectile: ProjectileConstructor, projectile_body: Body, projectile_light: Option, - projectile_gravity: Option, projectile_speed: f32, reps_remaining: u32, }, @@ -197,7 +195,6 @@ pub enum CharacterAbility { recover_duration: f32, projectile_body: Body, projectile_light: Option, - projectile_gravity: Option, initial_projectile_speed: f32, scaled_projectile_speed: f32, move_speed: f32, @@ -1163,7 +1160,6 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { projectile, projectile_body, projectile_light, - projectile_gravity, projectile_speed, energy_cost: _, } => CharacterState::BasicRanged(basic_ranged::Data { @@ -1173,7 +1169,6 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { projectile: *projectile, projectile_body: *projectile_body, projectile_light: *projectile_light, - projectile_gravity: *projectile_gravity, projectile_speed: *projectile_speed, ability_info, }, @@ -1416,7 +1411,6 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { recover_duration, projectile_body, projectile_light, - projectile_gravity, initial_projectile_speed, scaled_projectile_speed, move_speed, @@ -1433,7 +1427,6 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { scaled_knockback: *scaled_knockback, projectile_body: *projectile_body, projectile_light: *projectile_light, - projectile_gravity: *projectile_gravity, initial_projectile_speed: *initial_projectile_speed, scaled_projectile_speed: *scaled_projectile_speed, move_speed: *move_speed, @@ -1453,7 +1446,6 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { projectile, projectile_body, projectile_light, - projectile_gravity, projectile_speed, reps_remaining, } => CharacterState::RepeaterRanged(repeater_ranged::Data { @@ -1466,7 +1458,6 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { projectile: *projectile, projectile_body: *projectile_body, projectile_light: *projectile_light, - projectile_gravity: *projectile_gravity, projectile_speed: *projectile_speed, ability_info, }, diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index d1f124f184..4da8d22f83 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -16,6 +16,7 @@ pub mod theropod; use crate::{ assets::{self, Asset}, + consts::{HUMAN_DENSITY, WATER_DENSITY}, make_case_elim, npc::NpcKind, }; @@ -24,7 +25,7 @@ use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; use vek::*; -use super::BuffKind; +use super::{BuffKind, Density, Mass}; make_case_elim!( body, @@ -145,165 +146,228 @@ impl< impl Body { pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) } - // Note: this might need to be refined to something more complex for realistic - // behavior with less cylindrical bodies (e.g. wolfs) - #[allow(unreachable_patterns)] - pub fn radius(&self) -> f32 { - // TODO: Improve these values (some might be reliant on more info in inner type) - match self { - Body::Humanoid(humanoid) => match (humanoid.species, humanoid.body_type) { - (humanoid::Species::Orc, humanoid::BodyType::Male) => 0.75, - (humanoid::Species::Orc, humanoid::BodyType::Female) => 0.75, - (humanoid::Species::Human, humanoid::BodyType::Male) => 0.75, - (humanoid::Species::Human, humanoid::BodyType::Female) => 0.75, - (humanoid::Species::Elf, humanoid::BodyType::Male) => 0.75, - (humanoid::Species::Elf, humanoid::BodyType::Female) => 0.75, - (humanoid::Species::Dwarf, humanoid::BodyType::Male) => 0.75, - (humanoid::Species::Dwarf, humanoid::BodyType::Female) => 0.75, - (humanoid::Species::Undead, humanoid::BodyType::Male) => 0.75, - (humanoid::Species::Undead, humanoid::BodyType::Female) => 0.75, - (humanoid::Species::Danari, humanoid::BodyType::Male) => 0.75, - (humanoid::Species::Danari, humanoid::BodyType::Female) => 0.75, - _ => 0.75, - }, - Body::QuadrupedSmall(_) => 0.6, - Body::QuadrupedMedium(body) => match body.species { - quadruped_medium::Species::Grolgar => 2.0, - quadruped_medium::Species::Tarasque => 2.0, - quadruped_medium::Species::Lion => 2.0, - quadruped_medium::Species::Saber => 2.0, - quadruped_medium::Species::Catoblepas => 2.0, - quadruped_medium::Species::Horse => 1.5, - quadruped_medium::Species::Deer => 1.5, - quadruped_medium::Species::Donkey => 1.5, - quadruped_medium::Species::Kelpie => 1.5, - quadruped_medium::Species::Barghest => 1.8, - quadruped_medium::Species::Cattle => 1.8, - quadruped_medium::Species::Highland => 1.8, - quadruped_medium::Species::Yak => 1.8, - quadruped_medium::Species::Panda => 1.8, - quadruped_medium::Species::Bear => 1.8, - _ => 1.5, - }, - Body::QuadrupedLow(body) => match body.species { - quadruped_low::Species::Asp => 2.5, - quadruped_low::Species::Monitor => 2.3, - quadruped_low::Species::Crocodile => 2.4, - quadruped_low::Species::Salamander => 2.4, - quadruped_low::Species::Pangolin => 2.0, - quadruped_low::Species::Lavadrake => 2.5, - quadruped_low::Species::Deadwood => 0.5, - _ => 1.6, - }, - Body::Theropod(body) => match body.species { - theropod::Species::Snowraptor => 1.5, - theropod::Species::Sandraptor => 1.5, - theropod::Species::Woodraptor => 1.5, - theropod::Species::Archaeos => 3.5, - theropod::Species::Odonto => 3.5, - theropod::Species::Yale => 0.8, - theropod::Species::Ntouka => 3.0, - _ => 1.8, - }, - Body::BirdMedium(_) => 1.0, - Body::FishMedium(_) => 1.0, - Body::Dragon(_) => 8.0, - Body::BirdSmall(_) => 0.6, - Body::FishSmall(_) => 0.6, - Body::BipedLarge(body) => match body.species { - biped_large::Species::Slysaurok => 2.0, - biped_large::Species::Occultsaurok => 2.0, - biped_large::Species::Mightysaurok => 2.0, - biped_large::Species::Mindflayer => 2.2, - biped_large::Species::Minotaur => 3.0, + /// Average density of the body + // Units are based on kg/m³ + pub fn density(&self) -> Density { + let d = match self { + // based on a house sparrow (Passer domesticus) + Body::BirdMedium(_) => 700.0, + Body::BirdSmall(_) => 700.0, - _ => 2.3, - }, - Body::Golem(_) => 2.5, - Body::BipedSmall(_) => 0.75, - Body::Object(_) => 0.4, - Body::Ship(_) => 1.0, - } + // based on its mass divided by the volume of a bird scaled up to the size of the dragon + Body::Dragon(_) => 3_700.0, + + Body::Golem(_) => WATER_DENSITY * 2.5, + Body::Humanoid(_) => HUMAN_DENSITY, + Body::Ship(ship) => ship.density().0, + Body::Object(object) => object.density().0, + _ => HUMAN_DENSITY, + }; + Density(d) } - pub fn height(&self) -> f32 { - match self { - Body::Humanoid(humanoid) => match (humanoid.species, humanoid.body_type) { - (humanoid::Species::Orc, humanoid::BodyType::Male) => 2.3, - (humanoid::Species::Orc, humanoid::BodyType::Female) => 2.2, - (humanoid::Species::Human, humanoid::BodyType::Male) => 2.3, - (humanoid::Species::Human, humanoid::BodyType::Female) => 2.2, - (humanoid::Species::Elf, humanoid::BodyType::Male) => 2.3, - (humanoid::Species::Elf, humanoid::BodyType::Female) => 2.2, - (humanoid::Species::Dwarf, humanoid::BodyType::Male) => 1.9, - (humanoid::Species::Dwarf, humanoid::BodyType::Female) => 1.8, - (humanoid::Species::Undead, humanoid::BodyType::Male) => 2.2, - (humanoid::Species::Undead, humanoid::BodyType::Female) => 2.1, - (humanoid::Species::Danari, humanoid::BodyType::Male) => 1.5, - (humanoid::Species::Danari, humanoid::BodyType::Female) => 1.4, + // Values marked with ~✅ are checked based on their RL equivalent. + // Discrepancy in size compared to their RL equivalents has not necessarily been + // taken into account. + pub fn mass(&self) -> Mass { + let m = match self { + Body::BipedLarge(body) => match body.species { + biped_large::Species::Slysaurok => 400.0, + biped_large::Species::Occultsaurok => 400.0, + biped_large::Species::Mightysaurok => 400.0, + biped_large::Species::Mindflayer => 420.0, + biped_large::Species::Minotaur => 500.0, + _ => 400.0, + }, + Body::BipedSmall(_) => 50.0, + + // ravens are 0.69-2 kg, crows are 0.51 kg on average + Body::BirdMedium(_) => 1.0, + // australian magpies are around 0.22-0.35 kg + Body::BirdSmall(_) => 0.3, + + Body::Dragon(_) => 20_000.0, + Body::FishMedium(_) => 2.5, + Body::FishSmall(_) => 1.0, + Body::Golem(_) => 10_000.0, + Body::Humanoid(humanoid) => { + // humanoids are quite a bit larger than in real life, so we multiply their mass + // to scale it up proportionally (remember cube law) + 1.0 * match (humanoid.species, humanoid.body_type) { + (humanoid::Species::Orc, humanoid::BodyType::Male) => 120.0, + (humanoid::Species::Orc, humanoid::BodyType::Female) => 120.0, + (humanoid::Species::Human, humanoid::BodyType::Male) => 77.0, // ~✅ + (humanoid::Species::Human, humanoid::BodyType::Female) => 59.0, // ~✅ + (humanoid::Species::Elf, humanoid::BodyType::Male) => 77.0, + (humanoid::Species::Elf, humanoid::BodyType::Female) => 59.0, + (humanoid::Species::Dwarf, humanoid::BodyType::Male) => 70.0, + (humanoid::Species::Dwarf, humanoid::BodyType::Female) => 70.0, + (humanoid::Species::Undead, humanoid::BodyType::Male) => 70.0, + (humanoid::Species::Undead, humanoid::BodyType::Female) => 50.0, + (humanoid::Species::Danari, humanoid::BodyType::Male) => 80.0, + (humanoid::Species::Danari, humanoid::BodyType::Female) => 60.0, + } + }, + Body::Object(obj) => obj.mass().0, + Body::QuadrupedLow(body) => match body.species { + quadruped_low::Species::Alligator => 360.0, // ~✅ + quadruped_low::Species::Asp => 300.0, + // saltwater crocodiles can weigh around 1 ton, but our version is the size of an + // alligator or smaller, so whatever + quadruped_low::Species::Crocodile => 360.0, + quadruped_low::Species::Deadwood => 400.0, + quadruped_low::Species::Lavadrake => 500.0, + quadruped_low::Species::Monitor => 100.0, + quadruped_low::Species::Pangolin => 100.0, + quadruped_low::Species::Salamander => 65.0, + quadruped_low::Species::Tortoise => 200.0, + _ => 200.0, + }, + Body::QuadrupedMedium(body) => match body.species { + quadruped_medium::Species::Bear => 500.0, // ~✅ (350-700 kg) + quadruped_medium::Species::Cattle => 575.0, // ~✅ (500-650 kg) + quadruped_medium::Species::Deer => 80.0, + quadruped_medium::Species::Donkey => 200.0, + quadruped_medium::Species::Highland => 200.0, + quadruped_medium::Species::Horse => 500.0, // ~✅ + quadruped_medium::Species::Kelpie => 200.0, + quadruped_medium::Species::Lion => 170.0, // ~✅ (110-225 kg) + quadruped_medium::Species::Panda => 200.0, + quadruped_medium::Species::Saber => 130.0, + quadruped_medium::Species::Yak => 200.0, + _ => 200.0, }, Body::QuadrupedSmall(body) => match body.species { - quadruped_small::Species::Dodarock => 1.5, - quadruped_small::Species::Holladon => 1.5, - quadruped_small::Species::Truffler => 2.0, - _ => 1.0, - }, - Body::QuadrupedMedium(body) => match body.species { - quadruped_medium::Species::Tarasque => 2.6, - quadruped_medium::Species::Lion => 2.0, - quadruped_medium::Species::Saber => 2.0, - quadruped_medium::Species::Catoblepas => 2.9, - quadruped_medium::Species::Barghest => 2.5, - quadruped_medium::Species::Dreadhorn => 2.5, - quadruped_medium::Species::Moose => 2.5, - _ => 1.6, - }, - Body::QuadrupedLow(body) => match body.species { - quadruped_low::Species::Monitor => 1.5, - quadruped_low::Species::Tortoise => 2.0, - quadruped_low::Species::Rocksnapper => 2.9, - quadruped_low::Species::Maneater => 4.0, - _ => 1.3, + quadruped_small::Species::Batfox => 50.0, + quadruped_small::Species::Boar => 80.0, // ~✅ (60-100 kg) + quadruped_small::Species::Dodarock => 150.0, + quadruped_small::Species::Holladon => 150.0, + quadruped_small::Species::Hyena => 70.0, // ~✅ (vaguely) + quadruped_small::Species::Truffler => 150.0, + _ => 80.0, }, Body::Theropod(body) => match body.species { - theropod::Species::Snowraptor => 2.6, - theropod::Species::Sandraptor => 2.6, - theropod::Species::Woodraptor => 2.6, - theropod::Species::Sunlizard => 2.5, - theropod::Species::Yale => 3.0, - _ => 8.0, - }, - Body::BirdMedium(body) => match body.species { - bird_medium::Species::Cockatrice => 1.8, - _ => 1.1, - }, - Body::FishMedium(_) => 0.8, - Body::Dragon(_) => 16.0, - Body::BirdSmall(_) => 1.1, - Body::FishSmall(_) => 0.6, - Body::BipedLarge(body) => match body.species { - biped_large::Species::Slysaurok => 3.4, - biped_large::Species::Occultsaurok => 3.4, - biped_large::Species::Mightysaurok => 3.4, - biped_large::Species::Mindflayer => 8.0, - biped_large::Species::Minotaur => 8.0, - biped_large::Species::Dullahan => 5.5, - biped_large::Species::Cyclops => 6.5, - biped_large::Species::Werewolf => 3.5, + // for reference, elephants are in the range of 2.6-6.9 tons + // and Tyrannosaurus rex were ~8.4-14 tons + theropod::Species::Archaeos => 13_000.0, + theropod::Species::Ntouka => 13_000.0, + theropod::Species::Odonto => 13_000.0, - _ => 6.0, + theropod::Species::Sandraptor => 500.0, + theropod::Species::Snowraptor => 500.0, + theropod::Species::Sunlizard => 500.0, + theropod::Species::Woodraptor => 500.0, + theropod::Species::Yale => 1_000.0, }, - Body::Golem(_) => 5.0, - Body::BipedSmall(_) => 1.4, - Body::Object(object) => match object { - object::Body::Crossbow => 1.7, - object::Body::TrainingDummy => 2.2, - _ => 1.0, + Body::Ship(ship) => ship.mass().0, + }; + Mass(m) + } + + /// The width (shoulder to shoulder), length (nose to tail) and height + /// respectively + pub fn dimensions(&self) -> Vec3 { + match self { + Body::BipedLarge(body) => match body.species { + biped_large::Species::Cyclops => Vec3::new(4.6, 3.0, 6.5), + biped_large::Species::Dullahan => Vec3::new(4.6, 3.0, 5.5), + biped_large::Species::Mightysaurok => Vec3::new(4.0, 3.0, 3.4), + biped_large::Species::Mindflayer => Vec3::new(4.4, 3.0, 8.0), + biped_large::Species::Minotaur => Vec3::new(6.0, 3.0, 8.0), + biped_large::Species::Occultsaurok => Vec3::new(4.0, 3.0, 3.4), + biped_large::Species::Slysaurok => Vec3::new(4.0, 3.0, 3.4), + biped_large::Species::Werewolf => Vec3::new(4.0, 3.0, 3.5), + + _ => Vec3::new(4.6, 3.0, 6.0), + }, + Body::BipedSmall(_) => Vec3::new(1.0, 0.75, 1.4), + Body::BirdMedium(body) => match body.species { + bird_medium::Species::Cockatrice => Vec3::new(2.0, 1.0, 1.8), + _ => Vec3::new(2.0, 1.0, 1.1), + }, + Body::BirdSmall(_) => Vec3::new(1.2, 0.6, 1.1), + Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0), + Body::FishMedium(_) => Vec3::new(0.5, 2.0, 0.8), + Body::FishSmall(_) => Vec3::new(0.3, 1.2, 0.6), + Body::Golem(_) => Vec3::new(5.0, 5.0, 5.0), + Body::Humanoid(humanoid) => { + let height = match (humanoid.species, humanoid.body_type) { + (humanoid::Species::Orc, humanoid::BodyType::Male) => 2.3, + (humanoid::Species::Orc, humanoid::BodyType::Female) => 2.2, + (humanoid::Species::Human, humanoid::BodyType::Male) => 2.3, + (humanoid::Species::Human, humanoid::BodyType::Female) => 2.2, + (humanoid::Species::Elf, humanoid::BodyType::Male) => 2.3, + (humanoid::Species::Elf, humanoid::BodyType::Female) => 2.2, + (humanoid::Species::Dwarf, humanoid::BodyType::Male) => 1.9, + (humanoid::Species::Dwarf, humanoid::BodyType::Female) => 1.8, + (humanoid::Species::Undead, humanoid::BodyType::Male) => 2.2, + (humanoid::Species::Undead, humanoid::BodyType::Female) => 2.1, + (humanoid::Species::Danari, humanoid::BodyType::Male) => 1.5, + (humanoid::Species::Danari, humanoid::BodyType::Female) => 1.4, + }; + Vec3::new(1.5, 0.5, height) + }, + Body::Object(object) => object.dimensions(), + Body::QuadrupedMedium(body) => match body.species { + quadruped_medium::Species::Barghest => Vec3::new(2.0, 3.6, 2.5), + quadruped_medium::Species::Bear => Vec3::new(2.0, 3.6, 2.0), + quadruped_medium::Species::Catoblepas => Vec3::new(2.0, 4.0, 2.9), + quadruped_medium::Species::Cattle => Vec3::new(2.0, 3.6, 2.0), + quadruped_medium::Species::Deer => Vec3::new(2.0, 3.0, 2.0), + quadruped_medium::Species::Dreadhorn => Vec3::new(2.0, 3.0, 2.5), + quadruped_medium::Species::Grolgar => Vec3::new(2.0, 4.0, 2.0), + quadruped_medium::Species::Highland => Vec3::new(2.0, 3.6, 2.0), + quadruped_medium::Species::Horse => Vec3::new(2.0, 3.0, 2.0), + quadruped_medium::Species::Lion => Vec3::new(2.0, 3.0, 2.0), + quadruped_medium::Species::Moose => Vec3::new(2.0, 4.0, 2.5), + quadruped_medium::Species::Saber => Vec3::new(2.0, 4.0, 2.0), + quadruped_medium::Species::Tarasque => Vec3::new(2.0, 4.0, 2.6), + quadruped_medium::Species::Yak => Vec3::new(2.0, 3.6, 2.0), + _ => Vec3::new(2.0, 3.0, 2.0), + }, + Body::QuadrupedSmall(body) => match body.species { + quadruped_small::Species::Dodarock => Vec3::new(1.2, 1.2, 1.5), + quadruped_small::Species::Holladon => Vec3::new(1.2, 1.2, 1.5), + quadruped_small::Species::Truffler => Vec3::new(1.2, 1.2, 2.0), + _ => Vec3::new(1.2, 1.2, 1.0), + }, + Body::QuadrupedLow(body) => match body.species { + quadruped_low::Species::Asp => Vec3::new(1.0, 2.5, 1.3), + quadruped_low::Species::Crocodile => Vec3::new(1.0, 2.4, 1.3), + quadruped_low::Species::Deadwood => Vec3::new(1.0, 0.5, 1.3), + quadruped_low::Species::Lavadrake => Vec3::new(1.0, 2.5, 1.3), + quadruped_low::Species::Maneater => Vec3::new(1.0, 1.6, 4.0), + quadruped_low::Species::Monitor => Vec3::new(1.0, 2.3, 1.5), + quadruped_low::Species::Pangolin => Vec3::new(1.0, 2.0, 1.3), + quadruped_low::Species::Rocksnapper => Vec3::new(1.0, 1.6, 2.9), + quadruped_low::Species::Salamander => Vec3::new(1.0, 2.4, 1.3), + quadruped_low::Species::Tortoise => Vec3::new(1.0, 1.6, 2.0), + _ => Vec3::new(1.0, 1.6, 1.3), + }, + Body::Ship(ship) => ship.dimensions(), + Body::Theropod(body) => match body.species { + theropod::Species::Archaeos => Vec3::new(4.0, 7.0, 8.0), + theropod::Species::Ntouka => Vec3::new(4.0, 6.0, 8.0), + theropod::Species::Odonto => Vec3::new(4.0, 6.5, 8.0), + theropod::Species::Sandraptor => Vec3::new(2.0, 3.0, 2.6), + theropod::Species::Snowraptor => Vec3::new(2.0, 3.0, 2.6), + theropod::Species::Sunlizard => Vec3::new(2.0, 3.6, 2.5), + theropod::Species::Woodraptor => Vec3::new(2.0, 3.0, 2.6), + theropod::Species::Yale => Vec3::new(1.5, 3.2, 6.0), }, - Body::Ship(_) => 1.0, } } + // Note: This is used for collisions, but it's not very accurate for shapes that + // are very much not cylindrical. Eventually this ought to be replaced by more + // accurate collision shapes. + pub fn radius(&self) -> f32 { + let dim = self.dimensions(); + dim.x.max(dim.y) / 2.0 + } + + pub fn height(&self) -> f32 { self.dimensions().z } + pub fn base_energy(&self) -> u32 { match self { Body::BipedLarge(biped_large) => match biped_large.species { diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index 4ffaec7afc..605194aea0 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -1,6 +1,11 @@ -use crate::{comp::item::Reagent, make_case_elim}; +use crate::{ + comp::{item::Reagent, Density, Mass}, + consts::{IRON_DENSITY, WATER_DENSITY}, + make_case_elim, +}; use rand::{seq::SliceRandom, thread_rng}; use serde::{Deserialize, Serialize}; +use vek::Vec3; make_case_elim!( body, @@ -241,4 +246,43 @@ impl Body { Reagent::Yellow => Body::FireworkYellow, } } + + pub fn density(&self) -> Density { + let density = match self { + Body::Anvil | Body::Cauldron => IRON_DENSITY, + Body::Arrow | Body::MultiArrow => 500.0, + Body::Bomb => 2000.0, // I have no idea what it's supposed to be + Body::Crate => 300.0, // let's say it's a lot of wood and maybe some contents + Body::Scarecrow => 900.0, + Body::TrainingDummy => 2000.0, + // let them sink + _ => 1.1 * WATER_DENSITY, + }; + + Density(density) + } + + pub fn mass(&self) -> Mass { + let m = match self { + // I think MultiArrow is one of several arrows, not several arrows combined? + Body::Arrow | Body::MultiArrow => 0.003, + Body::Bomb => { + 0.5 * IRON_DENSITY * std::f32::consts::PI / 6.0 * self.dimensions().x.powi(3) + }, + Body::Scarecrow => 50.0, + Body::Cauldron => 5.0, + Body::TrainingDummy => 60.0, + _ => 1.0, + }; + + Mass(m) + } + + pub fn dimensions(&self) -> Vec3 { + match self { + Body::Arrow | Body::ArrowSnake | Body::MultiArrow => Vec3::new(0.01, 0.8, 0.01), + Body::BoltFire => Vec3::new(0.1, 0.1, 0.1), + _ => Vec3::broadcast(0.2), + } + } } diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 68532b677b..c67085acce 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -1,5 +1,10 @@ -use crate::make_case_elim; +use crate::{ + comp::{Density, Mass}, + consts::AIR_DENSITY, + make_case_elim, +}; use serde::{Deserialize, Serialize}; +use vek::Vec3; make_case_elim!( body, @@ -20,6 +25,33 @@ impl Body { Body::DefaultAirship => "Human_Airship", } } + + pub fn dimensions(&self) -> Vec3 { Vec3::new(25.0, 50.0, 40.0) } + + fn balloon_vol(&self) -> f32 { + let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 { + (std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d + }; + let dim = self.dimensions(); + spheroid_vol(dim.z, dim.y) + } + + fn hull_vol(&self) -> f32 { + // height from bottom of keel to deck + let deck_height = 10_f32; + let dim = self.dimensions(); + (std::f32::consts::PI / 6.0) * (deck_height * 1.5).powi(2) * dim.y + } + + pub fn hull_density(&self) -> Density { + let oak_density = 600_f32; + let ratio = 0.1; + Density(ratio * oak_density + (1.0 - ratio) * AIR_DENSITY) + } + + pub fn density(&self) -> Density { Density(AIR_DENSITY) } + + pub fn mass(&self) -> Mass { Mass((self.hull_vol() + self.balloon_vol()) * self.density().0) } } /// Terrain is 11.0 scale relative to small-scale voxels, and all figures get diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index f0a9b9db51..587b3f5b1f 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -1,6 +1,6 @@ use crate::{ combat::Attack, - comp::{tool::ToolKind, Energy, InputAttr, InputKind, Ori, Pos, Vel}, + comp::{tool::ToolKind, Density, Energy, InputAttr, InputKind, Ori, Pos, Vel}, event::{LocalEvent, ServerEvent}, states::{behavior::JoinData, *}, }; @@ -16,6 +16,7 @@ pub struct StateUpdate { pub pos: Pos, pub vel: Vel, pub ori: Ori, + pub density: Density, pub energy: Energy, pub swap_equipped_weapons: bool, pub queued_inputs: BTreeMap, @@ -30,6 +31,7 @@ impl From<&JoinData<'_>> for StateUpdate { pos: *data.pos, vel: *data.vel, ori: *data.ori, + density: *data.density, energy: *data.energy, swap_equipped_weapons: false, character: data.character.clone(), diff --git a/common/src/comp/fluid_dynamics.rs b/common/src/comp/fluid_dynamics.rs new file mode 100644 index 0000000000..6e5a688240 --- /dev/null +++ b/common/src/comp/fluid_dynamics.rs @@ -0,0 +1,204 @@ +use super::{ + body::{object, Body}, + Density, Vel, +}; +use crate::{ + consts::{AIR_DENSITY, WATER_DENSITY}, + util::Dir, +}; +use serde::{Deserialize, Serialize}; +use std::f32::consts::PI; +use vek::*; + +/// Fluid medium in which the entity exists +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Fluid { + Air { vel: Vel, elevation: f32 }, + Water { vel: Vel, depth: f32 }, +} + +impl Fluid { + /// Specific mass + pub fn density(&self) -> Density { + match self { + Self::Air { .. } => Density(AIR_DENSITY), + Self::Water { .. } => Density(WATER_DENSITY), + } + } + + /// Pressure from entity velocity + pub fn dynamic_pressure(&self, vel: &Vel) -> f32 { + 0.5 * self.density().0 * self.relative_flow(vel).0.magnitude_squared() + } + + /* + pub fn static_pressure(&self) -> f32 { + match self { + Self::Air { elevation, .. } => Self::air_pressure(*elevation), + Self::Water { depth, .. } => Self::water_pressure(*depth), + } + } + + /// Absolute static pressure of air at elevation + pub fn air_pressure(elevation: f32) -> f32 { + // At low altitudes above sea level, the pressure decreases by about 1.2 kPa for + // every 100 metres. + // https://en.wikipedia.org/wiki/Atmospheric_pressure#Altitude_variation + ATMOSPHERE - elevation / 12.0 + } + + /// Absolute static pressure of water at depth + pub fn water_pressure(depth: f32) -> f32 { WATER_DENSITY * GRAVITY * depth + ATMOSPHERE } + */ + /// Velocity of fluid, if applicable + pub fn flow_vel(&self) -> Vel { + match self { + Self::Air { vel, .. } => *vel, + Self::Water { vel, .. } => *vel, + } + } + + // Very simple but useful in reducing mental overhead + pub fn relative_flow(&self, vel: &Vel) -> Vel { Vel(self.flow_vel().0 - vel.0) } + + pub fn is_liquid(&self) -> bool { matches!(self, Fluid::Water { .. }) } + + pub fn elevation(&self) -> Option { + match self { + Fluid::Air { elevation, .. } => Some(*elevation), + _ => None, + } + } + + pub fn depth(&self) -> Option { + match self { + Fluid::Water { depth, .. } => Some(*depth), + _ => None, + } + } +} + +impl Default for Fluid { + fn default() -> Self { + Self::Air { + elevation: 0.0, + vel: Vel::zero(), + } + } +} + +impl Body { + pub fn aerodynamic_forces(&self, rel_flow: &Vel, fluid_density: f32) -> Vec3 { + let v_sq = rel_flow.0.magnitude_squared(); + if v_sq < 0.25 { + // don't bother with miniscule forces + 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 * self.parasite_drag_coefficient() * *rel_flow_dir + } + } + + /// Parasite drag is the sum of pressure drag and skin friction. + /// 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 { + // Reference area and drag coefficient assumes best-case scenario of the + // orientation producing least amount of drag + match self { + // Cross-section, head/feet first + Body::BipedLarge(_) | Body::BipedSmall(_) | Body::Golem(_) | Body::Humanoid(_) => { + let dim = self.dimensions().xy().map(|a| a * 0.5); + 0.7 * PI * dim.x * dim.y + }, + + // Cross-section, nose/tail first + Body::Theropod(_) + | Body::QuadrupedMedium(_) + | Body::QuadrupedSmall(_) + | Body::QuadrupedLow(_) => { + let dim = self.dimensions().map(|a| a * 0.5); + let cd = if matches!(self, Body::QuadrupedLow(_)) { + 0.7 + } else { + 1.0 + }; + cd * std::f32::consts::PI * dim.x * dim.z + }, + + // Cross-section, zero-lift angle; exclude the wings (width * 0.2) + Body::BirdMedium(_) | Body::BirdSmall(_) | Body::Dragon(_) => { + let dim = self.dimensions().map(|a| a * 0.5); + let cd = match self { + Body::BirdMedium(_) => 0.2, + Body::BirdSmall(_) => 0.4, + _ => 0.7, + }; + cd * std::f32::consts::PI * dim.x * 0.2 * dim.z + }, + + // Cross-section, zero-lift angle; exclude the fins (width * 0.2) + Body::FishMedium(_) | Body::FishSmall(_) => { + let dim = self.dimensions().map(|a| a * 0.5); + 0.031 * std::f32::consts::PI * dim.x * 0.2 * dim.z + }, + + Body::Object(object) => match object { + // very streamlined objects + object::Body::Arrow + | object::Body::ArrowSnake + | object::Body::FireworkBlue + | object::Body::FireworkGreen + | object::Body::FireworkPurple + | object::Body::FireworkRed + | object::Body::FireworkWhite + | object::Body::FireworkYellow + | object::Body::MultiArrow => { + let dim = self.dimensions().map(|a| a * 0.5); + 0.02 * std::f32::consts::PI * dim.x * dim.z + }, + + // spherical-ish objects + object::Body::BoltFire + | object::Body::BoltFireBig + | object::Body::BoltNature + | object::Body::Bomb + | object::Body::PotionBlue + | object::Body::PotionGreen + | object::Body::PotionRed + | object::Body::Pouch + | object::Body::Pumpkin + | object::Body::Pumpkin2 + | object::Body::Pumpkin3 + | object::Body::Pumpkin4 + | object::Body::Pumpkin5 => { + let dim = self.dimensions().map(|a| a * 0.5); + 0.5 * std::f32::consts::PI * dim.x * dim.z + }, + + _ => { + let dim = self.dimensions(); + 2.0 * (std::f32::consts::PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0) + }, + }, + + Body::Ship(_) => { + // Airships tend to use the square of the cube root of its volume for + // reference area + let dim = self.dimensions(); + (std::f32::consts::PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0) + }, + } + } +} + +/* +## References: + +1. "Field Estimates of Body Drag Coefficient on the Basis of Dives in Passerine Birds", + Anders Hedenström and Felix Liechti, 2001 +2. "A Simple Method to Determine Drag Coefficients in Aquatic Animals", + D. Bilo and W. Nachtigall, 1980 +*/ diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 00ea323c21..2c3664a2c7 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -14,6 +14,7 @@ pub mod compass; mod controller; pub mod dialogue; #[cfg(not(target_arch = "wasm32"))] mod energy; +pub mod fluid_dynamics; #[cfg(not(target_arch = "wasm32"))] pub mod group; mod health; #[cfg(not(target_arch = "wasm32"))] @@ -67,6 +68,7 @@ pub use self::{ InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting, }, energy::{Energy, EnergyChange, EnergySource}, + fluid_dynamics::Fluid, group::Group, home_chunk::HomeChunk, inputs::CanBuild, @@ -79,7 +81,7 @@ pub use self::{ misc::Object, ori::Ori, phys::{ - Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PosVelDefer, PreviousPhysCache, + Collider, Density, ForceUpdate, Mass, PhysicsState, Pos, PosVelDefer, PreviousPhysCache, Scale, Sticky, Vel, }, player::Player, diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index 1961a04256..6acd70b359 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -148,6 +148,10 @@ impl Ori { self.to_quat() * local } + pub fn to_horizontal(self) -> Option { + Dir::from_unnormalized(self.look_dir().xy().into()).map(|ori| ori.into()) + } + pub fn pitched_up(self, angle_radians: f32) -> Self { self.rotated(Quaternion::rotation_x(angle_radians)) } @@ -252,7 +256,7 @@ impl From for vek::vec::repr_simd::Vec3 { } impl From for Vec2 { - fn from(ori: Ori) -> Self { ori.look_vec().xy() } + fn from(ori: Ori) -> Self { ori.look_dir().to_horizontal().unwrap_or_default().xy() } } impl From for vek::vec::repr_simd::Vec2 { diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 88a30b8040..e03552722b 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -1,4 +1,5 @@ -use crate::uid::Uid; +use super::Fluid; +use crate::{consts::WATER_DENSITY, uid::Uid}; use hashbrown::HashSet; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage, NullStorage}; @@ -19,6 +20,10 @@ impl Component for Pos { #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Vel(pub Vec3); +impl Vel { + pub fn zero() -> Self { Vel(Vec3::zero()) } +} + impl Component for Vel { // TODO: why not regular vec storage???? type Storage = IdvStorage; @@ -66,13 +71,30 @@ impl Component for Scale { } // Mass -#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Mass(pub f32); +impl Default for Mass { + fn default() -> Mass { Mass(1.0) } +} + impl Component for Mass { type Storage = DerefFlaggedStorage>; } +/// The average density (specific mass) of an entity. +/// Units used for reference is kg/m³ +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Density(pub f32); + +impl Default for Density { + fn default() -> Density { Density(WATER_DENSITY) } +} + +impl Component for Density { + type Storage = DerefFlaggedStorage>; +} + // Collider #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Collider { @@ -92,6 +114,11 @@ impl Collider { } } + pub fn get_height(&self) -> f32 { + let (z_min, z_max) = self.get_z_limits(1.0); + z_max - z_min + } + pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { match self { Collider::Voxel { .. } => (0.0, 1.0), @@ -105,13 +132,6 @@ impl Component for Collider { type Storage = DerefFlaggedStorage>; } -#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct Gravity(pub f32); - -impl Component for Gravity { - type Storage = DerefFlaggedStorage>; -} - #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Sticky; @@ -126,7 +146,7 @@ pub struct PhysicsState { pub on_ceiling: bool, pub on_wall: Option>, pub touch_entities: HashSet, - pub in_liquid: Option, // Depth + pub in_fluid: Option, pub ground_vel: Vec3, } @@ -149,6 +169,8 @@ impl PhysicsState { .or_else(|| self.on_ceiling.then_some(Vec3::unit_z())) .or(self.on_wall) } + + pub fn in_liquid(&self) -> Option { self.in_fluid.and_then(|fluid| fluid.depth()) } } impl Component for PhysicsState { diff --git a/common/src/consts.rs b/common/src/consts.rs index 1e0079b70f..2e4897696b 100644 --- a/common/src/consts.rs +++ b/common/src/consts.rs @@ -2,5 +2,17 @@ pub const MAX_PICKUP_RANGE: f32 = 8.0; pub const MAX_MOUNT_RANGE: f32 = 14.0; -pub const GRAVITY: f32 = 9.81 * 5.0; +pub const GRAVITY: f32 = 25.0; pub const FRIC_GROUND: f32 = 0.15; + +// Values for air taken from http://www-mdp.eng.cam.ac.uk/web/library/enginfo/aerothermal_dvd_only/aero/atmos/atmos.html +// Values below are for dry air at 15°C, sea level, 1 standard atmosphere + +// pub const ATMOSPHERE: f32 = 101325.0; // Pa + +// kg/m³ +pub const AIR_DENSITY: f32 = 1.225; +pub const WATER_DENSITY: f32 = 999.1026; +pub const IRON_DENSITY: f32 = 7870.0; +// pub const HUMAN_DENSITY: f32 = 1010.0; // real value +pub const HUMAN_DENSITY: f32 = 990.0; // value we use to make humanoids gently float diff --git a/common/src/event.rs b/common/src/event.rs index bf5d43f3f4..a67e6113dc 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -63,7 +63,6 @@ pub enum ServerEvent { body: comp::Body, light: Option, projectile: comp::Projectile, - gravity: Option, speed: f32, object: Option, }, diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index 0565d37ff9..2c3b59883f 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Body, CharacterState, Gravity, LightEmitter, ProjectileConstructor, StateUpdate}, + comp::{Body, CharacterState, LightEmitter, ProjectileConstructor, StateUpdate}, event::ServerEvent, states::{ behavior::{CharacterBehavior, JoinData}, @@ -20,7 +20,6 @@ pub struct StaticData { pub projectile: ProjectileConstructor, pub projectile_body: Body, pub projectile_light: Option, - pub projectile_gravity: Option, pub projectile_speed: f32, /// What key is used to press ability pub ability_info: AbilityInfo, @@ -82,7 +81,6 @@ impl CharacterBehavior for Data { body: self.static_data.projectile_body, projectile, light: self.static_data.projectile_light, - gravity: self.static_data.projectile_gravity, speed: self.static_data.projectile_speed, object: None, }); diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs index 0735b01bad..075c15fe88 100644 --- a/common/src/states/behavior.rs +++ b/common/src/states/behavior.rs @@ -1,8 +1,8 @@ use crate::{ comp::{ self, item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, - Controller, ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory, - InventoryAction, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel, + Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory, + InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel, }, resources::DeltaTime, uid::Uid, @@ -80,6 +80,8 @@ pub struct JoinData<'a> { pub pos: &'a Pos, pub vel: &'a Vel, pub ori: &'a Ori, + pub mass: &'a Mass, + pub density: &'a Density, pub dt: &'a DeltaTime, pub controller: &'a Controller, pub inputs: &'a ControllerInputs, @@ -113,6 +115,8 @@ pub struct JoinStruct<'a> { pub pos: &'a mut Pos, pub vel: &'a mut Vel, pub ori: &'a mut Ori, + pub mass: &'a Mass, + pub density: &'a mut Density, pub energy: RestrictedMut<'a, Energy>, pub inventory: RestrictedMut<'a, Inventory>, pub controller: &'a mut Controller, @@ -141,6 +145,8 @@ impl<'a> JoinData<'a> { pos: j.pos, vel: j.vel, ori: j.ori, + mass: j.mass, + density: j.density, energy: j.energy.get_unchecked(), inventory: j.inventory.get_unchecked(), controller: j.controller, diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index b62b18f5ef..9f7434ae03 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -4,8 +4,8 @@ use crate::{ DamageSource, GroupTarget, Knockback, KnockbackDir, }, comp::{ - projectile, Body, CharacterState, EnergyChange, EnergySource, Gravity, LightEmitter, - Projectile, StateUpdate, + projectile, Body, CharacterState, EnergyChange, EnergySource, LightEmitter, Projectile, + StateUpdate, }, event::ServerEvent, states::{ @@ -40,7 +40,6 @@ pub struct StaticData { /// Projectile information pub projectile_body: Body, pub projectile_light: Option, - pub projectile_gravity: Option, pub initial_projectile_speed: f32, pub scaled_projectile_speed: f32, /// Move speed efficiency @@ -138,7 +137,6 @@ impl CharacterBehavior for Data { body: self.static_data.projectile_body, projectile, light: self.static_data.projectile_light, - gravity: self.static_data.projectile_gravity, speed: self.static_data.initial_projectile_speed + charge_frac * self.static_data.scaled_projectile_speed, object: None, diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index 4508f01664..712516c4de 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -64,12 +64,15 @@ impl CharacterBehavior for Data { ) { (wall_dir, climb) } else { - if input_is_pressed(data, InputKind::Jump) { + if let Some(impulse) = input_is_pressed(data, InputKind::Jump) + .then(|| data.body.jump_impulse()) + .flatten() + { // They've climbed atop something, give them a boost update .local_events - .push_front(LocalEvent::Jump(data.entity, BASE_JUMP_IMPULSE * 0.5)); - } + .push_front(LocalEvent::Jump(data.entity, 0.5 * impulse / data.mass.0)); + }; update.character = CharacterState::Idle {}; return update; }; diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 04bfbb8fe6..7835c33463 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -7,11 +7,11 @@ use crate::{ use serde::{Deserialize, Serialize}; use vek::Vec2; -// Gravity is 9.81 * 4, so this makes gravity equal to .15 const GLIDE_ANTIGRAV: f32 = crate::consts::GRAVITY * 0.90; const GLIDE_ACCEL: f32 = 5.0; +const GLIDE_MAX_SPEED: f32 = 30.0; -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data; impl CharacterBehavior for Data { @@ -25,7 +25,7 @@ impl CharacterBehavior for Data { } if data .physics - .in_liquid + .in_liquid() .map(|depth| depth > 0.5) .unwrap_or(false) { @@ -34,21 +34,21 @@ impl CharacterBehavior for Data { if data.inventory.equipped(EquipSlot::Glider).is_none() { update.character = CharacterState::Idle }; - // If there is a wall in front of character and they are trying to climb go to - // climb - handle_climb(&data, &mut update); + + let horiz_vel = Vec2::::from(update.vel.0); + let horiz_speed_sq = horiz_vel.magnitude_squared(); // Move player according to movement direction vector - update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL; + if horiz_speed_sq < GLIDE_MAX_SPEED.powi(2) { + update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL; + } // Determine orientation vector from movement direction vector - let horiz_vel = Vec2::::from(update.vel.0); if let Some(dir) = Dir::from_unnormalized(update.vel.0) { update.ori = update.ori.slerped_towards(Ori::from(dir), 2.0 * data.dt.0); }; // Apply Glide antigrav lift - let horiz_speed_sq = horiz_vel.magnitude_squared(); if update.vel.0.z < 0.0 { let lift = (GLIDE_ANTIGRAV + update.vel.0.z.powi(2) * 0.15) * (horiz_speed_sq * f32::powf(0.075, 2.0)).clamp(0.2, 1.0); @@ -56,6 +56,10 @@ impl CharacterBehavior for Data { update.vel.0.z += lift * data.dt.0; } + // If there is a wall in front of character and they are trying to climb go to + // climb + handle_climb(&data, &mut update); + update } diff --git a/common/src/states/glide_wield.rs b/common/src/states/glide_wield.rs index 90a6627b67..68512accc5 100644 --- a/common/src/states/glide_wield.rs +++ b/common/src/states/glide_wield.rs @@ -32,7 +32,7 @@ impl CharacterBehavior for Data { } if data .physics - .in_liquid + .in_liquid() .map(|depth| depth > 0.5) .unwrap_or(false) { diff --git a/common/src/states/repeater_ranged.rs b/common/src/states/repeater_ranged.rs index 56ab2cadee..9c298fc1a0 100644 --- a/common/src/states/repeater_ranged.rs +++ b/common/src/states/repeater_ranged.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Body, CharacterState, Gravity, LightEmitter, ProjectileConstructor, StateUpdate}, + comp::{Body, CharacterState, LightEmitter, ProjectileConstructor, StateUpdate}, event::ServerEvent, states::{ behavior::{CharacterBehavior, JoinData}, @@ -28,7 +28,6 @@ pub struct StaticData { pub projectile: ProjectileConstructor, pub projectile_body: Body, pub projectile_light: Option, - pub projectile_gravity: Option, pub projectile_speed: f32, /// What key is used to press ability pub ability_info: AbilityInfo, @@ -155,7 +154,6 @@ impl CharacterBehavior for Data { body: self.static_data.projectile_body, projectile, light: self.static_data.projectile_light, - gravity: self.static_data.projectile_gravity, speed: self.static_data.projectile_speed, object: None, }); diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index dfaa5e9848..654fa44ade 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -5,8 +5,8 @@ use crate::{ item::{Hands, ItemKind, Tool, ToolKind}, quadruped_low, quadruped_medium, quadruped_small, ship, skills::{Skill, SwimSkill}, - theropod, Body, CharacterAbility, CharacterState, InputAttr, InputKind, InventoryAction, - StateUpdate, + theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, + InventoryAction, StateUpdate, }, consts::{FRIC_GROUND, GRAVITY}, event::{LocalEvent, ServerEvent}, @@ -18,23 +18,6 @@ use std::time::Duration; use vek::*; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; -const BASE_HUMANOID_AIR_ACCEL: f32 = 2.0; -const BASE_FLIGHT_ACCEL: f32 = 2.0; -const BASE_HUMANOID_WATER_ACCEL: f32 = 150.0; -const BASE_HUMANOID_WATER_SPEED: f32 = 180.0; -pub const BASE_JUMP_IMPULSE: f32 = 16.0; -// const BASE_HUMANOID_CLIMB_ACCEL: f32 = 10.0; -// const ROLL_SPEED: f32 = 17.0; -// const CHARGE_SPEED: f32 = 20.0; -// const GLIDE_ACCEL: f32 = 15.0; -// const GLIDE_SPEED: f32 = 45.0; -// const BLOCK_ACCEL: f32 = 30.0; -// const BLOCK_SPEED: f32 = 75.0; -// Gravity is 9.81 * 4, so this makes gravity equal to .15 //TODO: <- is wrong -// -// const GLIDE_ANTIGRAV: f32 = GRAVITY * 0.96; -// const CLIMB_SPEED: f32 = 5.0; -// const CLIMB_COST: i32 = 5; impl Body { pub fn base_accel(&self) -> f32 { @@ -119,7 +102,7 @@ impl Body { quadruped_low::Species::Basilisk => 120.0, quadruped_low::Species::Deadwood => 140.0, }, - Body::Ship(_) => 30.0, + Body::Ship(_) => 0.0, } } @@ -128,8 +111,7 @@ impl Body { pub fn max_speed_approx(&self) -> f32 { // Inverse kinematics: at what velocity will acceleration // be cancelled out by friction drag? - // Note: we assume no air (this is fine, current physics - // uses max(air_drag, ground_drag)). + // Note: we assume no air, since it's such a small factor. // Derived via... // v = (v + dv / 30) * (1 - drag).powi(2) (accel cancels drag) // => 1 = (1 + (dv / 30) / v) * (1 - drag).powi(2) @@ -142,53 +124,94 @@ impl Body { v } + /// The turn rate in 180°/s (or (rotations per second)/2) pub fn base_ori_rate(&self) -> f32 { match self { - Body::Humanoid(_) => 20.0, - Body::QuadrupedSmall(_) => 15.0, - Body::QuadrupedMedium(_) => 8.0, - Body::BirdMedium(_) => 30.0, - Body::FishMedium(_) => 5.0, - Body::Dragon(_) => 5.0, - Body::BirdSmall(_) => 35.0, - Body::FishSmall(_) => 10.0, - Body::BipedLarge(_) => 8.0, - Body::BipedSmall(_) => 12.0, - Body::Object(_) => 10.0, - Body::Golem(_) => 8.0, + Body::Humanoid(_) => 4.0, + Body::QuadrupedSmall(_) => 3.0, + Body::QuadrupedMedium(_) => 1.6, + Body::BirdMedium(_) => 6.0, + Body::FishMedium(_) => 6.0, + Body::Dragon(_) => 1.0, + Body::BirdSmall(_) => 7.0, + Body::FishSmall(_) => 7.0, + Body::BipedLarge(_) => 1.6, + Body::BipedSmall(_) => 2.4, + Body::Object(_) => 2.0, + Body::Golem(_) => 0.8, Body::Theropod(theropod) => match theropod.species { - theropod::Species::Archaeos => 2.5, - theropod::Species::Odonto => 2.5, - theropod::Species::Ntouka => 2.5, - _ => 7.0, + theropod::Species::Archaeos => 0.5, + theropod::Species::Odonto => 0.5, + theropod::Species::Ntouka => 0.5, + _ => 1.4, }, Body::QuadrupedLow(quadruped_low) => match quadruped_low.species { - quadruped_low::Species::Monitor => 9.0, - quadruped_low::Species::Asp => 8.0, - quadruped_low::Species::Tortoise => 3.0, - quadruped_low::Species::Rocksnapper => 4.0, - quadruped_low::Species::Maneater => 5.0, - quadruped_low::Species::Lavadrake => 4.0, - _ => 6.0, + quadruped_low::Species::Monitor => 1.8, + quadruped_low::Species::Asp => 1.6, + quadruped_low::Species::Tortoise => 0.6, + quadruped_low::Species::Rocksnapper => 0.8, + quadruped_low::Species::Maneater => 1.0, + quadruped_low::Species::Lavadrake => 0.8, + _ => 1.2, }, - Body::Ship(_) => 0.175, + Body::Ship(_) => 0.035, } } - /// Returns flying speed if the body type can fly, otherwise None - pub fn can_fly(&self) -> Option { + /// Returns thrust force if the body type can swim, otherwise None + pub fn swim_thrust(&self) -> Option { match self { - Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) => Some(1.0), - Body::Ship(ship::Body::DefaultAirship) => Some(1.0), + Body::Object(_) | Body::Ship(_) => None, + Body::BipedLarge(_) | Body::Golem(_) => Some(200.0 * self.mass().0), + Body::BipedSmall(_) => Some(100.0 * self.mass().0), + Body::BirdMedium(_) => Some(50.0 * self.mass().0), + Body::BirdSmall(_) => Some(50.0 * self.mass().0), + 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::Theropod(body) => match body.species { + theropod::Species::Sandraptor + | theropod::Species::Snowraptor + | theropod::Species::Sunlizard + | theropod::Species::Woodraptor + | theropod::Species::Yale => Some(200.0 * self.mass().0), + _ => Some(100.0 * self.mass().0), + }, + Body::QuadrupedLow(_) => Some(300.0 * self.mass().0), + Body::QuadrupedMedium(_) => Some(300.0 * self.mass().0), + Body::QuadrupedSmall(_) => Some(300.0 * self.mass().0), + } + } + + /// Returns thrust force if the body type can fly, otherwise None + pub fn fly_thrust(&self) -> Option { + match self { + Body::BirdMedium(_) => Some(GRAVITY * self.mass().0 * 2.0), + Body::BirdSmall(_) => Some(GRAVITY * self.mass().0 * 2.0), + Body::Dragon(_) => Some(200_000.0), + Body::Ship(ship::Body::DefaultAirship) => Some(300_000.0), _ => None, } } + /// Returns jump impulse if the body type can jump, otherwise None pub fn jump_impulse(&self) -> Option { match self { Body::Object(_) | Body::Ship(_) => None, - _ => Some(BASE_JUMP_IMPULSE), + Body::BipedLarge(_) | Body::Dragon(_) | Body::Golem(_) | Body::QuadrupedLow(_) => { + Some(0.1 * self.mass().0) + }, + Body::QuadrupedMedium(_) => Some(0.4 * self.mass().0), + Body::Theropod(body) => match body.species { + theropod::Species::Snowraptor + | theropod::Species::Sandraptor + | theropod::Species::Woodraptor => Some(0.4 * self.mass().0), + _ => None, + }, + _ => Some(0.4 * self.mass().0), } + .map(|f| f * GRAVITY) } pub fn can_climb(&self) -> bool { matches!(self, Body::Humanoid(_)) } @@ -196,21 +219,22 @@ impl Body { /// Handles updating `Components` to move player based on state of `JoinData` pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { - if let Some(depth) = data.physics.in_liquid { - swim_move(data, update, efficiency, depth); - } else if input_is_pressed(data, InputKind::Fly) + let submersion = data + .physics + .in_liquid() + .map(|depth| depth / data.body.height()); + + if input_is_pressed(data, InputKind::Fly) + && submersion.map_or(true, |sub| sub < 1.0) && (!data.physics.on_ground || data.body.jump_impulse().is_none()) - && data.body.can_fly().is_some() + && data.body.fly_thrust().is_some() { - fly_move( - data, - update, - efficiency - * data - .body - .can_fly() - .expect("can_fly is_some right above this"), - ); + fly_move(data, update, efficiency); + } else if let Some(submersion) = (!data.physics.on_ground && data.body.swim_thrust().is_some()) + .then_some(submersion) + .flatten() + { + swim_move(data, update, efficiency, submersion); } else { basic_move(data, update, efficiency); } @@ -219,16 +243,23 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { /// Updates components to move player as if theyre on ground or in air #[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587 fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { - let accel = if data.physics.on_ground { - data.body.base_accel() - } else { - BASE_HUMANOID_AIR_ACCEL - }; + handle_orientation(data, update, efficiency); - update.vel.0 = - update.vel.0 + Vec2::broadcast(data.dt.0) * data.inputs.move_dir * accel * efficiency; - - handle_orientation(data, update, data.body.base_ori_rate()); + if let Some(accel) = data + .physics + .on_ground + .then_some(data.body.base_accel() * efficiency) + { + // Should ability to backpedal be separate from ability to strafe? + update.vel.0 += Vec2::broadcast(data.dt.0) + * accel + * if data.body.can_strafe() { + data.inputs.move_dir + } else { + let fw = Vec2::from(update.ori); + fw * data.inputs.move_dir.dot(fw).max(0.0) + }; + } } /// Handles forced movement @@ -238,17 +269,15 @@ pub fn handle_forced_movement( movement: ForcedMovement, efficiency: f32, ) { + handle_orientation(data, update, efficiency); + match movement { ForcedMovement::Forward { strength } => { - let accel = if data.physics.on_ground { - data.body.base_accel() - } else { - BASE_HUMANOID_AIR_ACCEL - }; - - update.vel.0 += Vec2::broadcast(data.dt.0) - * accel - * (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength); + if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) { + update.vel.0 += Vec2::broadcast(data.dt.0) + * accel + * (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength); + } }, ForcedMovement::Leap { vertical, @@ -268,86 +297,129 @@ pub fn handle_forced_movement( // Apply direction + Vec3::from(dir) // Multiply by forward leap strength - * forward + * forward // Control forward movement based on look direction. // This allows players to stop moving forward when they // look downward at target - * (1.0 - data.inputs.look_dir.z.abs()); + * (1.0 - data.inputs.look_dir.z.abs()); }, ForcedMovement::Hover { move_input } => { update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0) + move_input * data.inputs.move_dir.try_normalized().unwrap_or_default(); }, } - handle_orientation(data, update, data.body.base_ori_rate() * efficiency); } -pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32) { - // Set direction based on move direction - let ori_dir = if (update.character.is_aimed() && data.body.can_strafe()) - || update.character.is_attack() +pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { + let strafe_aim = update.character.is_aimed() && data.body.can_strafe(); + if let Some(dir) = (strafe_aim || update.character.is_attack()) + .then(|| data.inputs.look_dir.to_horizontal().unwrap_or_default()) + .or_else(|| Dir::from_unnormalized(data.inputs.move_dir.into())) { - data.inputs.look_dir.xy() - } else if !data.inputs.move_dir.is_approx_zero() { - data.inputs.move_dir - } else { - update.ori.into() + let rate = { + let angle = update.ori.look_dir().angle_between(*dir); + data.body.base_ori_rate() * efficiency * std::f32::consts::PI / angle + }; + update.ori = update + .ori + .slerped_towards(dir.into(), (data.dt.0 * rate).min(0.1)); }; - - // Smooth orientation - update.ori = Dir::slerp_to_vec3(update.ori.look_dir(), ori_dir.into(), rate * data.dt.0).into(); } /// Updates components to move player as if theyre swimming -fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) { - let mut water_accel = BASE_HUMANOID_WATER_ACCEL; - let mut water_speed = BASE_HUMANOID_WATER_SPEED; - if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) { - water_speed *= 1.4_f32.powi(level.into()); - water_accel *= 1.4_f32.powi(level.into()); - } +fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submersion: f32) -> bool { + if let Some(force) = data.body.swim_thrust() { + handle_orientation(data, update, efficiency * 0.2); - // Update velocity - update.vel.0 += Vec2::broadcast(data.dt.0) - * data.inputs.move_dir - * if update.vel.0.magnitude_squared() < water_speed.powi(2) { - water_accel - } else { - 0.0 + let force = efficiency * force; + 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()); } - * efficiency; - handle_orientation( - data, - update, - data.body.base_ori_rate() * if data.physics.on_ground { 0.5 } else { 0.1 }, - ); + let dir = if data.body.can_strafe() { + data.inputs.move_dir + } else { + let fw = Vec2::from(update.ori); + fw * data.inputs.move_dir.dot(fw).max(0.0) + }; - // Swim - update.vel.0.z = (update.vel.0.z - + data.dt.0 - * GRAVITY - * 4.0 - * data - .inputs - .move_z - .clamped(-1.0, depth.clamped(0.0, 1.0).powi(3))) - .min(water_speed); + // Autoswim to stay afloat + let move_z = if submersion < 1.0 && data.inputs.move_z.abs() < std::f32::EPSILON { + (submersion - 0.1).max(0.0) + } else { + data.inputs.move_z + }; + + update.vel.0 += Vec3::broadcast(data.dt.0) + * Vec3::new(dir.x, dir.y, move_z) + .try_normalized() + .unwrap_or_default() + * water_accel + * (submersion - 0.2).clamp(0.0, 1.0).powi(2); + + true + } else { + false + } } /// Updates components to move entity as if it's flying -fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { - // Update velocity (counteract gravity with lift) - update.vel.0 += Vec3::unit_z() * data.dt.0 * GRAVITY - + Vec3::new( - data.inputs.move_dir.x, - data.inputs.move_dir.y, - data.inputs.move_z, - ) * data.dt.0 - * BASE_FLIGHT_ACCEL - * efficiency; +pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool { + if let Some(force) = data.body.fly_thrust() { + let thrust = efficiency * force; - handle_orientation(data, update, data.body.base_ori_rate()); + let accel = thrust / data.mass.0; + + handle_orientation(data, update, efficiency); + + // Elevation control + match data.body { + // flappy flappy + Body::Dragon(_) | Body::BirdMedium(_) | Body::BirdSmall(_) => { + let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0)); + update.vel.0.z += data.dt.0 * (anti_grav + accel * data.inputs.move_z.max(0.0)); + }, + // floaty floaty + Body::Ship(ship @ ship::Body::DefaultAirship) => { + let regulate_density = |min: f32, max: f32, def: f32, rate: f32| -> Density { + // Reset to default on no input + let change = if data.inputs.move_z.abs() > std::f32::EPSILON { + -data.inputs.move_z + } else { + (def - data.density.0).max(-1.0).min(1.0) + }; + Density((update.density.0 + data.dt.0 * rate * change).clamp(min, max)) + }; + let def_density = ship.density().0; + if data.physics.in_liquid().is_some() { + let hull_density = ship.hull_density().0; + update.density.0 = + regulate_density(def_density * 0.6, hull_density, hull_density, 25.0).0; + } else { + update.density.0 = + regulate_density(def_density * 0.5, def_density * 1.5, def_density, 0.5).0; + }; + }, + // oopsie woopsie + // TODO: refactor to make this state impossible + _ => {}, + }; + + update.vel.0 += Vec2::broadcast(data.dt.0) + * accel + * if data.body.can_strafe() { + data.inputs.move_dir + } else { + let fw = Vec2::from(update.ori); + fw * data.inputs.move_dir.dot(fw).max(0.0) + }; + + true + } else { + false + } } /// Checks if an input related to an attack is held. If one is, moves entity @@ -408,7 +480,7 @@ pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) { && !data.physics.on_ground && !data .physics - .in_liquid + .in_liquid() .map(|depth| depth > 1.0) .unwrap_or(false) //&& update.vel.0.z < 0.0 @@ -442,7 +514,7 @@ pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) { if data.inventory.equipped(EquipSlot::Glider).is_some() && !data .physics - .in_liquid + .in_liquid() .map(|depth| depth > 1.0) .unwrap_or(false) && data.body.is_humanoid() @@ -453,23 +525,16 @@ pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) { /// Checks that player can jump and sends jump event if so pub fn handle_jump(data: &JoinData, update: &mut StateUpdate, strength: f32) -> bool { - if input_is_pressed(data, InputKind::Jump) - && data.physics.on_ground - && !data - .physics - .in_liquid - .map(|depth| depth > 1.0) - .unwrap_or(false) - && data.body.jump_impulse().is_some() - { - update.local_events.push_front(LocalEvent::Jump( - data.entity, - data.body.jump_impulse().unwrap() * strength, - )); - true - } else { - false - } + (input_is_pressed(data, InputKind::Jump) && data.physics.on_ground) + .then(|| data.body.jump_impulse()) + .flatten() + .map(|impulse| { + update.local_events.push_front(LocalEvent::Jump( + data.entity, + strength * impulse / data.mass.0, + )); + }) + .is_some() } fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) { @@ -666,7 +731,12 @@ impl MovementDirection { pub fn get_2d_dir(self, data: &JoinData) -> Vec2 { use MovementDirection::*; match self { - Look => data.inputs.look_dir.xy(), + Look => data + .inputs + .look_dir + .to_horizontal() + .unwrap_or_default() + .xy(), Move => data.inputs.move_dir, } .try_normalized() diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index bf1460fc4f..34a299dd9b 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -112,6 +112,8 @@ impl Dir { pub fn back() -> Self { -Dir::new(Vec3::::unit_y()) } + pub fn to_horizontal(self) -> Option { Self::from_unnormalized(self.xy().into()) } + pub fn vec(&self) -> &Vec3 { &self.0 } pub fn to_vec(self) -> Vec3 { self.0 } diff --git a/common/state/src/lib.rs b/common/state/src/lib.rs index fca4e14f9e..c633e60771 100644 --- a/common/state/src/lib.rs +++ b/common/state/src/lib.rs @@ -199,9 +199,9 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); - ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/sys/Cargo.toml b/common/sys/Cargo.toml index 348379a164..33a746079d 100644 --- a/common/sys/Cargo.toml +++ b/common/sys/Cargo.toml @@ -31,4 +31,4 @@ slab = "0.4.2" specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control", "derive"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" } # Tweak running code -#inline_tweak = { version = "1.0.8", features = ["release_tweak"] } +# inline_tweak = { version = "1.0.8", features = ["release_tweak"] } diff --git a/common/sys/src/character_behavior.rs b/common/sys/src/character_behavior.rs index 77654af81d..251df59873 100644 --- a/common/sys/src/character_behavior.rs +++ b/common/sys/src/character_behavior.rs @@ -10,8 +10,9 @@ use common::{ item::MaterialStatManifest, slot::{EquipSlot, Slot}, }, - Beam, Body, CharacterState, Combo, Controller, Energy, Health, Inventory, Melee, Mounting, - Ori, PhysicsState, Poise, PoiseState, Pos, SkillSet, StateUpdate, Stats, Vel, + Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health, Inventory, Mass, + Melee, Mounting, Ori, PhysicsState, Poise, PoiseState, Pos, SkillSet, StateUpdate, Stats, + Vel, }, event::{EventBus, LocalEvent, ServerEvent}, resources::DeltaTime, @@ -32,6 +33,7 @@ fn incorporate_update(join: &mut JoinStruct, mut state_update: StateUpdate) { *join.pos = state_update.pos; *join.vel = state_update.vel; *join.ori = state_update.ori; + *join.density = state_update.density; // Note: might be changed every tick by timer anyway if join.energy.get_unchecked() != &state_update.energy { *join.energy.get_mut_unchecked() = state_update.energy @@ -67,6 +69,7 @@ pub struct ReadData<'a> { lazy_update: Read<'a, LazyUpdate>, healths: ReadStorage<'a, Health>, bodies: ReadStorage<'a, Body>, + masses: ReadStorage<'a, Mass>, physics_states: ReadStorage<'a, PhysicsState>, melee_attacks: ReadStorage<'a, Melee>, beams: ReadStorage<'a, Beam>, @@ -93,6 +96,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, + WriteStorage<'a, Density>, WriteStorage<'a, Energy>, WriteStorage<'a, Inventory>, WriteStorage<'a, Controller>, @@ -112,6 +116,7 @@ impl<'a> System<'a> for Sys { mut positions, mut velocities, mut orientations, + mut densities, mut energies, mut inventories, mut controllers, @@ -128,14 +133,15 @@ impl<'a> System<'a> for Sys { mut pos, mut vel, mut ori, + mass, + mut density, energy, inventory, mut controller, health, body, physics, - stat, - skill_set, + (stat, skill_set), combo, ) in ( &read_data.entities, @@ -144,14 +150,15 @@ impl<'a> System<'a> for Sys { &mut positions, &mut velocities, &mut orientations, + &read_data.masses, + &mut densities, &mut energies.restrict_mut(), &mut inventories.restrict_mut(), &mut controllers, read_data.healths.maybe(), &read_data.bodies, &read_data.physics_states, - &read_data.stats, - &read_data.skill_sets, + (&read_data.stats, &read_data.skill_sets), &read_data.combos, ) .join() @@ -253,6 +260,8 @@ impl<'a> System<'a> for Sys { pos: &mut pos, vel: &mut vel, ori: &mut ori, + mass: &mass, + density: &mut density, energy, inventory, controller: &mut controller, diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 8eb29bf80a..6d766b77fe 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -5,15 +5,17 @@ use spatial_grid::SpatialGrid; use common::{ comp::{ body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST}, - BeamSegment, Body, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, - Pos, PosVelDefer, PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, + BeamSegment, Body, CharacterState, Collider, Density, Fluid, Mass, Mounting, Ori, + PhysicsState, Pos, PosVelDefer, PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, + Vel, }, - consts::{FRIC_GROUND, GRAVITY}, + consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY}, event::{EventBus, ServerEvent}, outcome::Outcome, resources::DeltaTime, terrain::{Block, TerrainGrid}, uid::Uid, + util::Projection, vol::{BaseVol, ReadVol}, }; use common_base::{prof_span, span}; @@ -24,39 +26,84 @@ use specs::{ Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect, WriteStorage, }; -use std::ops::Range; +use std::{f32::consts::PI, ops::Range}; use vek::*; -pub const BOUYANCY: f32 = 1.0; -// Friction values used for linear damping. They are unitless quantities. The -// value of these quantities must be between zero and one. They represent the -// amount an object will slow down within 1/60th of a second. Eg. if the -// friction is 0.01, and the speed is 1.0, then after 1/60th of a second the -// speed will be 0.99. after 1 second the speed will be 0.54, which is 0.99 ^ -// 60. -pub const FRIC_AIR: f32 = 0.0025; -pub const FRIC_FLUID: f32 = 0.4; +/// The density of the fluid as a function of submersion ratio in given fluid +/// where it is assumed that any unsubmersed part is is air. +// This is a pretty silly way of doing it as it assumes everything is spherical +// in shape and uniform in mass distribution, but the result feels good enough. +// TODO: Make the shape a capsule? +fn fluid_density(height: f32, fluid: &Fluid) -> Density { + // If depth is less than our height (partial submersion), remove + // fluid density based on the ratio of displacement to full volume. + // The displacement is modelled as a gradually submersed sphere. + let immersion = fluid.depth().map_or(1.0, |depth| { + if height < depth { + 1.0 + } else { + let hemisphere_filled_vol = |r: f32, h: f32| -> f32 { + let r_ = (r.powi(2) - (r - h).powi(2)).sqrt(); + (1.0 / 6.0) * PI * h * (3.0 * r_.powi(2) + h.powi(2)) + }; + let hemisphere_vol = |r: f32| -> f32 { (2.0 / 3.0) * PI * r.powi(3) }; + let r = height / 2.0; + let sphere_vol = 2.0 * hemisphere_vol(r); + if depth < r { + hemisphere_filled_vol(r, depth) / sphere_vol + } else { + 1.0 - hemisphere_filled_vol(r, height - depth) / sphere_vol + } + } + }); -// Integrates forces, calculates the new velocity based off of the old velocity -// dt = delta time -// lv = linear velocity -// damp = linear damping -// Friction is a type of damping. -fn integrate_forces(dt: f32, mut lv: Vec3, grav: f32, damp: f32) -> Vec3 { - // Clamp dt to an effective 10 TPS, to prevent gravity from slamming the players - // into the floor when stationary if other systems cause the server to lag - // (as observed in the 0.9 release party). - let dt = dt.min(0.1); + Density(fluid.density().0 * immersion + AIR_DENSITY * (1.0 - immersion)) +} - // this is not linear damping, because it is proportional to the original - // velocity this "linear" damping in in fact, quite exponential. and thus - // must be interpolated accordingly - let linear_damp = (1.0 - damp.min(1.0)).powf(dt * 60.0); +#[allow(clippy::too_many_arguments)] +fn integrate_forces( + dt: &DeltaTime, + mut vel: Vel, + body: &Body, + density: &Density, + mass: &Mass, + fluid: &Fluid, + gravity: f32, +) -> Vel { + let dim = body.dimensions(); + let height = dim.z; + let rel_flow = fluid.relative_flow(&vel); + let fluid_density = fluid_density(height, fluid); + debug_assert!(mass.0 > 0.0); + debug_assert!(density.0 > 0.0); - // TODO: investigate if we can have air friction provide the neccessary limits - // here - lv.z = (lv.z - grav * dt).max(-80.0).min(lv.z); - lv * linear_damp + // Aerodynamic/hydrodynamic forces + if !rel_flow.0.is_approx_zero() { + debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or()); + let impulse = dt.0 * body.aerodynamic_forces(&rel_flow, fluid_density.0); + debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or()); + if !impulse.is_approx_zero() { + let new_v = vel.0 + impulse / mass.0; + // If the new velocity is in the opposite direction, it's because the forces + // involved are too high for the current tick to handle. We deal with this by + // removing the component of our velocity vector along the direction of force. + // This way we can only ever lose velocity and will never experience a reverse + // in direction from events such as falling into water at high velocities. + if new_v.dot(vel.0) < 0.0 { + vel.0 -= vel.0.projected(&impulse); + } else { + vel.0 = new_v; + } + }; + debug_assert!(!vel.0.map(|a| a.is_nan()).reduce_or()); + }; + + // Hydrostatic/aerostatic forces + // modify gravity to account for the effective density as a result of buoyancy + let down_force = dt.0 * gravity * (density.0 - fluid_density.0) / density.0; + vel.0.z -= down_force; + + vel } fn calc_z_limit( @@ -88,7 +135,6 @@ pub struct PhysicsRead<'a> { stickies: ReadStorage<'a, Sticky>, masses: ReadStorage<'a, Mass>, colliders: ReadStorage<'a, Collider>, - gravities: ReadStorage<'a, Gravity>, mountings: ReadStorage<'a, Mounting>, projectiles: ReadStorage<'a, Projectile>, beams: ReadStorage<'a, BeamSegment>, @@ -96,6 +142,7 @@ pub struct PhysicsRead<'a> { char_states: ReadStorage<'a, CharacterState>, bodies: ReadStorage<'a, Body>, character_states: ReadStorage<'a, CharacterState>, + densities: ReadStorage<'a, Density>, } #[derive(SystemData)] @@ -255,7 +302,7 @@ impl<'a> PhysicsData<'a> { positions, &mut write.velocities, previous_phys_cache, - read.masses.maybe(), + &read.masses, read.colliders.maybe(), !&read.mountings, read.stickies.maybe(), @@ -288,7 +335,6 @@ impl<'a> PhysicsData<'a> { char_state_maybe, )| { let z_limits = calc_z_limit(char_state_maybe, collider); - let mass = mass.map(|m| m.0).unwrap_or(previous_cache.scale); // Resets touch_entities in physics physics.touch_entities.clear(); @@ -320,13 +366,14 @@ impl<'a> PhysicsData<'a> { .get(entity) .zip(positions.get(entity)) .zip(previous_phys_cache.get(entity)) - .map(|((uid, pos), previous_cache)| { + .zip(read.masses.get(entity)) + .map(|(((uid, pos), previous_cache), mass)| { ( entity, uid, pos, previous_cache, - read.masses.get(entity), + mass, read.colliders.get(entity), read.char_states.get(entity), ) @@ -358,16 +405,6 @@ impl<'a> PhysicsData<'a> { let z_limits_other = calc_z_limit(char_state_other_maybe, collider_other); - let mass_other = mass_other - .map(|m| m.0) - .unwrap_or(previous_cache_other.scale); - // This check after the pos check, as we currently don't have - // that many - // massless entites [citation needed] - if mass_other == 0.0 { - return; - } - entity_entity_collision_checks += 1; const MIN_COLLISION_DIST: f32 = 0.3; @@ -418,8 +455,8 @@ impl<'a> PhysicsData<'a> { { let force = 400.0 * (collision_dist - diff.magnitude()) - * mass_other - / (mass + mass_other); + * mass_other.0 + / (mass.0 + mass_other.0); vel_delta += Vec3::from(diff.normalized()) * force * step_delta; @@ -542,10 +579,12 @@ impl<'a> PhysicsData<'a> { // We do this in a first pass because it helps keep things more stable for // entities that are anchored to other entities (such as airships). ( - &read.entities, positions, velocities, + &read.bodies, &write.physics_states, + &read.masses, + &read.densities, !&read.mountings, ) .par_join() @@ -554,38 +593,31 @@ impl<'a> PhysicsData<'a> { prof_span!(guard, "velocity update rayon job"); guard }, - |_guard, (entity, pos, vel, physics_state, _)| { + |_guard, (pos, vel, body, physics_state, mass, density, _)| { let in_loaded_chunk = read .terrain .get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) .is_some(); - // Integrate forces - // Friction is assumed to be a constant dependent on location - let friction = if physics_state.on_ground { 0.0 } else { FRIC_AIR } - // .max(if physics_state.on_ground { - // FRIC_GROUND - // } else { - // 0.0 - // }) - .max(if physics_state.in_liquid.is_some() { - FRIC_FLUID - } else { - 0.0 - }); - let downward_force = - if !in_loaded_chunk { - 0.0 // No gravity in unloaded chunks - } else if physics_state - .in_liquid - .map(|depth| depth > 0.75) - .unwrap_or(false) - { - (1.0 - BOUYANCY) * GRAVITY - } else { - GRAVITY - } * read.gravities.get(entity).map(|g| g.0).unwrap_or_default(); - vel.0 = integrate_forces(read.dt.0, vel.0, downward_force, friction); + // Apply physics only if in a loaded chunk + if in_loaded_chunk { + // Clamp dt to an effective 10 TPS, to prevent gravity from slamming the + // players into the floor when stationary if other systems cause the server + // to lag (as observed in the 0.9 release party). + let dt = DeltaTime(read.dt.0.min(0.1)); + + match physics_state.in_fluid { + None => { + vel.0.z -= dt.0 * GRAVITY; + }, + Some(fluid) => { + vel.0 = integrate_forces( + &dt, *vel, body, density, mass, &fluid, GRAVITY, + ) + .0 + }, + } + } }, ); @@ -671,8 +703,7 @@ impl<'a> PhysicsData<'a> { let mut tgt_pos = pos.0 + pos_delta; let was_on_ground = physics_state.on_ground; - - let block_snap = body.map_or(false, |body| body.jump_impulse().is_some()); + let block_snap = body.map_or(false, |b| !matches!(b, Body::Ship(_))); let climbing = character_state.map_or(false, |cs| matches!(cs, CharacterState::Climb(_))); @@ -816,11 +847,21 @@ impl<'a> PhysicsData<'a> { } } - physics_state.in_liquid = read + physics_state.in_fluid = read .terrain .get(pos.0.map(|e| e.floor() as i32)) .ok() - .and_then(|vox| vox.is_liquid().then_some(1.0)); + .and_then(|vox| vox.is_liquid().then_some(1.0)) + .map(|depth| Fluid::Water { + depth, + vel: Vel::zero(), + }) + .or_else(|| { + Some(Fluid::Air { + elevation: pos.0.z, + vel: Vel::zero(), + }) + }); tgt_pos = pos.0; }, @@ -989,12 +1030,19 @@ impl<'a> PhysicsData<'a> { .on_wall .map(|dir| ori_from.mul_direction(dir)) }); - physics_state.in_liquid = match ( - physics_state.in_liquid, - physics_state_delta.in_liquid, + physics_state.in_fluid = match ( + physics_state.in_fluid, + physics_state_delta.in_fluid, ) { - // this match computes `x <|> y <|> liftA2 max x y` - (Some(x), Some(y)) => Some(x.max(y)), + (Some(x), Some(y)) => x + .depth() + .and_then(|xh| { + y.depth() + .map(|yh| xh > yh) + .unwrap_or(true) + .then_some(x) + }) + .or(Some(y)), (x @ Some(_), _) => x, (_, y @ Some(_)) => y, _ => None, @@ -1108,7 +1156,7 @@ impl<'a> System<'a> for Sys { #[allow(clippy::too_many_arguments)] fn box_voxel_collision<'a, T: BaseVol + ReadVol>( - cylinder: (f32, f32, f32), + cylinder: (f32, f32, f32), // effective collision cylinder terrain: &'a T, entity: Entity, pos: &mut Pos, @@ -1450,8 +1498,27 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( physics_state.ground_vel = ground_vel; } - // Set in_liquid state - physics_state.in_liquid = max_liquid_z.map(|max_z| max_z - pos.0.z); + physics_state.in_fluid = max_liquid_z + .map(|max_z| max_z - pos.0.z) // NOTE: assumes min_z == 0.0 + .map(|depth| { + physics_state + .in_liquid() + // This is suboptimal because it doesn't check for true depth, + // so it can cause problems for situations like swimming down + // a river and spawning or teleporting in(/to) water + .map(|old_depth| (old_depth + old_pos.z - pos.0.z).max(depth)) + .unwrap_or(depth) + }) + .map(|depth| Fluid::Water { + depth, + vel: Vel::zero(), + }) + .or_else(|| { + Some(Fluid::Air { + elevation: pos.0.z, + vel: Vel::zero(), + }) + }); } fn voxel_collider_bounding_sphere( diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 284aebb86a..78aa93f131 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -7,7 +7,7 @@ use common::{ beam, buff::{BuffCategory, BuffData, BuffKind, BuffSource}, inventory::loadout::Loadout, - shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop, + shockwave, Agent, Alignment, Body, Health, HomeChunk, Inventory, Item, ItemDrop, LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, SkillSet, Stats, Vel, WaypointArea, }, @@ -163,7 +163,6 @@ pub fn handle_shoot( body: Body, light: Option, projectile: Projectile, - gravity: Option, speed: f32, object: Option, ) { @@ -200,9 +199,6 @@ pub fn handle_shoot( if let Some(light) = light { builder = builder.with(light) } - if let Some(gravity) = gravity { - builder = builder.with(gravity) - } if let Some(object) = object { builder = builder.with(object) } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index bf314d75cc..d046405393 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -478,8 +478,14 @@ pub fn handle_delete(server: &mut Server, entity: EcsEntity) { pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) { let state = &server.state; if vel.z <= -30.0 { - let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 7.5; + let mass = state + .ecs() + .read_storage::() + .get(entity) + .copied() + .unwrap_or_default(); let inventories = state.ecs().read_storage::(); + let falldmg = mass.0 * vel.z.powi(2) / 200.0; let stats = state.ecs().read_storage::(); // Handle health change if let Some(mut health) = state.ecs().write_storage::().get_mut(entity) { @@ -495,7 +501,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) // Handle poise change if let Some(mut poise) = state.ecs().write_storage::().get_mut(entity) { let poise_damage = PoiseChange { - amount: -(falldmg / 2.0) as i32, + amount: -(mass.0 * vel.magnitude_squared() / 1500.0) as i32, source: PoiseSource::Falling, }; let poise_change = poise_damage.modify_poise_damage(inventories.get(entity)); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 784a8ef483..c5eebe7e64 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -73,12 +73,9 @@ impl Server { body, light, projectile, - gravity, speed, object, - } => handle_shoot( - self, entity, dir, body, light, projectile, gravity, speed, object, - ), + } => handle_shoot(self, entity, dir, body, light, projectile, speed, object), ServerEvent::Shockwave { properties, pos, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 733d188a57..93dd7cdc77 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -184,6 +184,8 @@ impl StateExt for State { )) .unwrap_or_default(), ) + .with(body.mass()) + .with(body.density()) .with(match body { comp::Body::Ship(ship) => comp::Collider::Voxel { id: ship.manifest_entry().to_string(), @@ -208,7 +210,6 @@ impl StateExt for State { .with(health) .with(poise) .with(comp::Alignment::Npc) - .with(comp::Gravity(1.0)) .with(comp::CharacterState::default()) .with(inventory) .with(comp::Buffs::default()) @@ -217,19 +218,20 @@ impl StateExt for State { } fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder { + let body = comp::Body::Object(object); self.ecs_mut() .create_entity_synced() .with(pos) .with(comp::Vel(Vec3::zero())) .with(comp::Ori::default()) - .with(comp::Mass(5.0)) + .with(body.mass()) + .with(body.density()) .with(comp::Collider::Box { - radius: comp::Body::Object(object).radius(), + radius: body.radius(), z_min: 0.0, - z_max: comp::Body::Object(object).height(), + z_max: body.height(), }) - .with(comp::Body::Object(object)) - .with(comp::Gravity(1.0)) + .with(body) } fn create_ship( @@ -238,18 +240,19 @@ impl StateExt for State { ship: comp::ship::Body, mountable: bool, ) -> EcsEntityBuilder { + let body = comp::Body::Ship(ship); let mut builder = self .ecs_mut() .create_entity_synced() .with(pos) .with(comp::Vel(Vec3::zero())) .with(comp::Ori::default()) - .with(comp::Mass(50.0)) + .with(body.mass()) + .with(body.density()) .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string(), }) - .with(comp::Body::Ship(ship)) - .with(comp::Gravity(1.0)) + .with(body) .with(comp::Scale(comp::ship::AIRSHIP_SCALE)) .with(comp::Controller::default()) .with(comp::inventory::Inventory::new_empty()) @@ -279,7 +282,8 @@ impl StateExt for State { .with(pos) .with(vel) .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default()) - .with(comp::Mass(0.0)) + .with(body.mass()) + .with(body.density()) .with(comp::Collider::Point) .with(body) .with(projectile) @@ -406,7 +410,6 @@ impl StateExt for State { z_min: 0.0, z_max: 1.75, }); - self.write_component_ignore_entity_dead(entity, comp::Gravity(1.0)); self.write_component_ignore_entity_dead(entity, comp::CharacterState::default()); self.write_component_ignore_entity_dead(entity, comp::Alignment::Owned(player_uid)); self.write_component_ignore_entity_dead(entity, comp::Buffs::default()); @@ -450,6 +453,8 @@ impl StateExt for State { z_max: body.height(), }); self.write_component_ignore_entity_dead(entity, body); + self.write_component_ignore_entity_dead(entity, body.mass()); + self.write_component_ignore_entity_dead(entity, body.density()); let (health_level, energy_level) = ( skill_set .skill_level(Skill::General(GeneralSkill::HealthIncrease)) diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index b9f8c98add..b457329989 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -19,6 +19,7 @@ use common::{ InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, SkillSet, Stats, UnresolvedChatMsg, Vel, }, + consts::GRAVITY, effect::{BuffEffect, Effect}, event::{Emitter, EventBus, ServerEvent}, path::TraversalConfig, @@ -233,10 +234,10 @@ impl<'a> System<'a> for Sys { node_tolerance, slow_factor, on_ground: physics_state.on_ground, - in_liquid: physics_state.in_liquid.is_some(), + in_liquid: physics_state.in_liquid().is_some(), min_tgt_dist: 1.0, can_climb: body.map(|b| b.can_climb()).unwrap_or(false), - can_fly: body.map(|b| b.can_fly().is_some()).unwrap_or(false), + can_fly: body.map(|b| b.fly_thrust().is_some()).unwrap_or(false), }; if traversal_config.can_fly { @@ -743,7 +744,7 @@ impl<'a> AgentData<'a> { * speed.min(agent.rtsim_controller.speed_factor); self.jump_if(controller, bearing.z > 1.5 || self.traversal_config.can_fly); controller.inputs.climb = Some(comp::Climb::Up); - //.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid.is_some()); + //.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid().is_some()); controller.inputs.move_z = bearing.z + if self.traversal_config.can_fly { @@ -1543,38 +1544,36 @@ impl<'a> AgentData<'a> { 0.0 }; - // Hacky distance offset for ranged weapons. This is - // intentionally hacky for now before we make ranged - // NPCs lead targets and implement varying aiming - // skill - let distance_offset = match tactic { - Tactic::Bow => { - 0.0004 /* Yay magic numbers */ * self.pos.0.distance_squared(tgt_pos.0) - }, - Tactic::Staff => { - 0.0015 /* Yay magic numbers */ * self.pos.0.distance_squared(tgt_pos.0) - }, - Tactic::QuadLowRanged => { - 0.03 /* Yay magic numbers */ * self.pos.0.distance_squared(tgt_pos.0) - }, - _ => 0.0, - }; + let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0); - // Apply the distance and eye offsets to make the - // look_dir the vector from projectile launch to - // target point - if let Some(dir) = Dir::from_unnormalized( - Vec3::new( - tgt_pos.0.x, - tgt_pos.0.y, - tgt_pos.0.z + tgt_eye_offset + distance_offset, - ) - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), - ) { + // FIXME: Retrieve actual projectile speed! + // We have to assume projectiles are faster than base speed because there are + // skills that increase it, and in most cases this will cause agents to + // overshoot + if let Some(dir) = match tactic { + Tactic::Bow + | Tactic::FixedTurret + | Tactic::QuadLowRanged + | Tactic::QuadMedJump + | Tactic::RotatingTurret + | Tactic::Staff + | Tactic::Turret + if dist_sqrd > 0.0 => + { + aim_projectile( + 90.0, // + self.vel.0.magnitude(), + Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), + Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset), + ) + } + _ => Dir::from_unnormalized( + Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset) + - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), + ), + } { controller.inputs.look_dir = dir; } - let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0); - // Match on tactic. Each tactic has different controls // depending on the distance from the agent to the target match tactic { @@ -2633,3 +2632,18 @@ fn try_owner_alignment<'a>( } alignment } + +/// Projectile motion: Returns the direction to aim for the projectile to reach +/// target position. Does not take any forces but gravity into account. +fn aim_projectile(speed: f32, pos: Vec3, tgt: Vec3) -> Option { + let mut to_tgt = tgt - pos; + let dist_sqrd = to_tgt.xy().magnitude_squared(); + let u_sqrd = speed.powi(2); + to_tgt.z = (u_sqrd + - (u_sqrd.powi(2) - GRAVITY * (GRAVITY * dist_sqrd + 2.0 * to_tgt.z * u_sqrd)) + .sqrt() + .max(0.0)) + / GRAVITY; + + Dir::from_unnormalized(to_tgt) +} diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index 1ab72bec9a..0f388a223f 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -76,7 +76,7 @@ impl<'a> System<'a> for Sys { const ENABLE_RECURSIVE_FIREWORKS: bool = true; if ENABLE_RECURSIVE_FIREWORKS { use common::{ - comp::{object, Body, Gravity, LightEmitter, Projectile}, + comp::{object, Body, LightEmitter, Projectile}, util::Dir, }; use rand::Rng; @@ -132,7 +132,6 @@ impl<'a> System<'a> for Sys { owner: *owner, ignore_group: true, }, - gravity: Some(Gravity(1.0)), speed, object: Some(Object::Firework { owner: *owner, diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index a7aba73f9a..96c034e3c1 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,7 +1,7 @@ use common::{ comp::{ item::MaterialStatManifest, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, - Collider, Combo, Energy, Gravity, Group, Health, Inventory, Item, LightEmitter, Mass, + Collider, Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, MountState, Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel, }, @@ -58,9 +58,9 @@ pub struct TrackedComps<'a> { pub mount_state: ReadStorage<'a, MountState>, pub group: ReadStorage<'a, Group>, pub mass: ReadStorage<'a, Mass>, + pub density: ReadStorage<'a, Density>, pub collider: ReadStorage<'a, Collider>, pub sticky: ReadStorage<'a, Sticky>, - pub gravity: ReadStorage<'a, Gravity>, pub inventory: ReadStorage<'a, Inventory>, pub character_state: ReadStorage<'a, CharacterState>, pub shockwave: ReadStorage<'a, Shockwave>, @@ -143,6 +143,10 @@ impl<'a> TrackedComps<'a> { .cloned() .map(|c| comps.push(c.into())); self.mass.get(entity).copied().map(|c| comps.push(c.into())); + self.density + .get(entity) + .copied() + .map(|c| comps.push(c.into())); self.collider .get(entity) .cloned() @@ -151,10 +155,6 @@ impl<'a> TrackedComps<'a> { .get(entity) .copied() .map(|c| comps.push(c.into())); - self.gravity - .get(entity) - .copied() - .map(|c| comps.push(c.into())); self.inventory .get(entity) .cloned() @@ -201,9 +201,9 @@ pub struct ReadTrackers<'a> { pub mount_state: ReadExpect<'a, UpdateTracker>, pub group: ReadExpect<'a, UpdateTracker>, pub mass: ReadExpect<'a, UpdateTracker>, + pub density: ReadExpect<'a, UpdateTracker>, pub collider: ReadExpect<'a, UpdateTracker>, pub sticky: ReadExpect<'a, UpdateTracker>, - pub gravity: ReadExpect<'a, UpdateTracker>, pub character_state: ReadExpect<'a, UpdateTracker>, pub shockwave: ReadExpect<'a, UpdateTracker>, pub beam_segment: ReadExpect<'a, UpdateTracker>, @@ -241,9 +241,9 @@ impl<'a> ReadTrackers<'a> { .with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter) .with_component(&comps.uid, &*self.group, &comps.group, filter) .with_component(&comps.uid, &*self.mass, &comps.mass, filter) + .with_component(&comps.uid, &*self.density, &comps.density, filter) .with_component(&comps.uid, &*self.collider, &comps.collider, filter) .with_component(&comps.uid, &*self.sticky, &comps.sticky, filter) - .with_component(&comps.uid, &*self.gravity, &comps.gravity, filter) .with_component(&comps.uid, &*self.inventory, &comps.inventory, filter) .with_component( &comps.uid, @@ -279,9 +279,9 @@ pub struct WriteTrackers<'a> { mount_state: WriteExpect<'a, UpdateTracker>, group: WriteExpect<'a, UpdateTracker>, mass: WriteExpect<'a, UpdateTracker>, + density: WriteExpect<'a, UpdateTracker>, collider: WriteExpect<'a, UpdateTracker>, sticky: WriteExpect<'a, UpdateTracker>, - gravity: WriteExpect<'a, UpdateTracker>, inventory: WriteExpect<'a, UpdateTracker>, character_state: WriteExpect<'a, UpdateTracker>, shockwave: WriteExpect<'a, UpdateTracker>, @@ -309,9 +309,9 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers.mount_state.record_changes(&comps.mount_state); trackers.group.record_changes(&comps.group); trackers.mass.record_changes(&comps.mass); + trackers.density.record_changes(&comps.density); trackers.collider.record_changes(&comps.collider); trackers.sticky.record_changes(&comps.sticky); - trackers.gravity.record_changes(&comps.gravity); trackers.inventory.record_changes(&comps.inventory); trackers .character_state @@ -350,9 +350,9 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { log_counts!(mounting, "Mountings"); log_counts!(mount_state, "Mount States"); log_counts!(mass, "Masses"); + log_counts!(mass, "Densities"); log_counts!(collider, "Colliders"); log_counts!(sticky, "Stickies"); - log_counts!(gravity, "Gravitys"); log_counts!(loadout, "Loadouts"); log_counts!(character_state, "Character States"); log_counts!(shockwave, "Shockwaves"); @@ -380,9 +380,9 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); + world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); - world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 9db8168574..1816f057c8 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -122,11 +122,7 @@ impl EventMapper for MovementEventMapper { // it was dispatched internal_state.event = mapped_event; internal_state.on_ground = physics.on_ground; - if physics.in_liquid.is_some() { - internal_state.in_water = true; - } else { - internal_state.in_water = false; - } + internal_state.in_water = physics.in_liquid().is_some(); let dt = ecs.fetch::().0; internal_state.distance_travelled += vel.0.magnitude() * dt; } @@ -197,8 +193,8 @@ impl MovementEventMapper { underfoot_block_kind: BlockKind, ) -> SfxEvent { // Match run / roll / swim state - if physics_state.in_liquid.is_some() && vel.magnitude() > 0.1 - || !previous_state.in_water && physics_state.in_liquid.is_some() + if physics_state.in_liquid().is_some() && vel.magnitude() > 0.1 + || !previous_state.in_water && physics_state.in_liquid().is_some() { return SfxEvent::Swim; } else if physics_state.on_ground && vel.magnitude() > 0.1 @@ -240,7 +236,7 @@ impl MovementEventMapper { vel: Vec3, underfoot_block_kind: BlockKind, ) -> SfxEvent { - if physics_state.in_liquid.is_some() && vel.magnitude() > 0.1 { + if physics_state.in_liquid().is_some() && vel.magnitude() > 0.1 { SfxEvent::Swim } else if physics_state.on_ground && vel.magnitude() > 0.1 { match underfoot_block_kind { @@ -261,7 +257,7 @@ impl MovementEventMapper { vel: Vec3, underfoot_block_kind: BlockKind, ) -> SfxEvent { - if physics_state.in_liquid.is_some() && vel.magnitude() > 0.1 { + if physics_state.in_liquid().is_some() && vel.magnitude() > 0.1 { SfxEvent::Swim } else if physics_state.on_ground && vel.magnitude() > 0.1 { match underfoot_block_kind { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 6ef7a08958..5e931d8374 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -783,7 +783,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::character::StandAnimation::update_skeleton( @@ -1411,7 +1411,7 @@ impl FigureMgr { skeleton_attr, ), CharacterState::Wielding { .. } => { - if physics.in_liquid.is_some() { + if physics.in_liquid().is_some() { anim::character::SwimWieldAnimation::update_skeleton( &target_base, ( @@ -1571,7 +1571,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => { @@ -1773,7 +1773,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > 0.25, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => { @@ -2100,7 +2100,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => { @@ -2459,7 +2459,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton( @@ -2569,7 +2569,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Idle (_, false, _) => anim::fish_medium::IdleAnimation::update_skeleton( @@ -2658,7 +2658,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Idle (true, false, false) => anim::biped_small::IdleAnimation::update_skeleton( @@ -3003,7 +3003,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::dragon::IdleAnimation::update_skeleton( @@ -3098,7 +3098,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::theropod::IdleAnimation::update_skeleton( @@ -3287,7 +3287,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::bird_small::IdleAnimation::update_skeleton( @@ -3378,7 +3378,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Idle (_, false, _) => anim::fish_small::IdleAnimation::update_skeleton( @@ -3467,7 +3467,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Running (true, true, false) => anim::biped_large::RunAnimation::update_skeleton( @@ -3988,7 +3988,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::golem::IdleAnimation::update_skeleton( @@ -4174,7 +4174,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::object::IdleAnimation::update_skeleton( @@ -4303,7 +4303,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid().is_some(), // In water ) { // Standing (true, false, false) => anim::ship::IdleAnimation::update_skeleton(