mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Basic fluid dynamics and physical properties for entities
This commit is contained in:
parent
bcf9a8089f
commit
762c68cfbb
14
CHANGELOG.md
14
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
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -9,6 +9,5 @@ BasicRanged(
|
||||
),
|
||||
projectile_body: Object(Arrow),
|
||||
projectile_light: None,
|
||||
projectile_gravity: Some(Gravity(0.2)),
|
||||
projectile_speed: 100.0,
|
||||
)
|
||||
|
@ -8,6 +8,5 @@ BasicRanged(
|
||||
col: (0.0, 1.0, 0.33).into(),
|
||||
..Default::default()
|
||||
}),*/
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
)
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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"] }
|
||||
|
@ -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<comp::MountState>),
|
||||
Mounting(PhantomData<comp::Mounting>),
|
||||
Mass(PhantomData<comp::Mass>),
|
||||
Density(PhantomData<comp::Density>),
|
||||
Collider(PhantomData<comp::Collider>),
|
||||
Gravity(PhantomData<comp::Gravity>),
|
||||
Sticky(PhantomData<comp::Sticky>),
|
||||
CharacterState(PhantomData<comp::CharacterState>),
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
@ -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::<comp::MountState>(entity, world),
|
||||
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
||||
EcsCompPhantom::Density(_) => sync::handle_remove::<comp::Density>(entity, world),
|
||||
EcsCompPhantom::Collider(_) => sync::handle_remove::<comp::Collider>(entity, world),
|
||||
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
|
||||
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
|
||||
EcsCompPhantom::CharacterState(_) => {
|
||||
sync::handle_remove::<comp::CharacterState>(entity, world)
|
||||
|
@ -631,7 +631,8 @@ pub enum KnockbackDir {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Knockback {
|
||||
pub fn calculate_impulse(self, dir: Dir) -> Vec3<f32> {
|
||||
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)
|
||||
|
@ -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<LightEmitter>,
|
||||
projectile_gravity: Option<Gravity>,
|
||||
projectile_speed: f32,
|
||||
},
|
||||
RepeaterRanged {
|
||||
@ -87,7 +86,6 @@ pub enum CharacterAbility {
|
||||
projectile: ProjectileConstructor,
|
||||
projectile_body: Body,
|
||||
projectile_light: Option<LightEmitter>,
|
||||
projectile_gravity: Option<Gravity>,
|
||||
projectile_speed: f32,
|
||||
reps_remaining: u32,
|
||||
},
|
||||
@ -197,7 +195,6 @@ pub enum CharacterAbility {
|
||||
recover_duration: f32,
|
||||
projectile_body: Body,
|
||||
projectile_light: Option<LightEmitter>,
|
||||
projectile_gravity: Option<Gravity>,
|
||||
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,
|
||||
},
|
||||
|
@ -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,90 +146,152 @@ 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,
|
||||
/// 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,
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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::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::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::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,
|
||||
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::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 => 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,
|
||||
// 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,
|
||||
|
||||
_ => 2.3,
|
||||
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(_) => 2.5,
|
||||
Body::BipedSmall(_) => 0.75,
|
||||
Body::Object(_) => 0.4,
|
||||
Body::Ship(_) => 1.0,
|
||||
}
|
||||
Body::Ship(ship) => ship.mass().0,
|
||||
};
|
||||
Mass(m)
|
||||
}
|
||||
|
||||
pub fn height(&self) -> f32 {
|
||||
/// The width (shoulder to shoulder), length (nose to tail) and height
|
||||
/// respectively
|
||||
pub fn dimensions(&self) -> Vec3<f32> {
|
||||
match self {
|
||||
Body::Humanoid(humanoid) => match (humanoid.species, humanoid.body_type) {
|
||||
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,
|
||||
@ -241,69 +304,70 @@ impl Body {
|
||||
(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 => 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,
|
||||
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::Monitor => 1.5,
|
||||
quadruped_low::Species::Tortoise => 2.0,
|
||||
quadruped_low::Species::Rocksnapper => 2.9,
|
||||
quadruped_low::Species::Maneater => 4.0,
|
||||
_ => 1.3,
|
||||
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::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,
|
||||
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::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,
|
||||
}
|
||||
}
|
||||
|
||||
_ => 6.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(_) => 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 {
|
||||
|
@ -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<f32> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<f32> { 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
|
||||
|
@ -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<InputKind, InputAttr>,
|
||||
@ -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(),
|
||||
|
204
common/src/comp/fluid_dynamics.rs
Normal file
204
common/src/comp/fluid_dynamics.rs
Normal file
@ -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<f32> {
|
||||
match self {
|
||||
Fluid::Air { elevation, .. } => Some(*elevation),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn depth(&self) -> Option<f32> {
|
||||
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<f32> {
|
||||
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
|
||||
*/
|
@ -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,
|
||||
|
@ -148,6 +148,10 @@ impl Ori {
|
||||
self.to_quat() * local
|
||||
}
|
||||
|
||||
pub fn to_horizontal(self) -> Option<Self> {
|
||||
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<Ori> for vek::vec::repr_simd::Vec3<f32> {
|
||||
}
|
||||
|
||||
impl From<Ori> for Vec2<f32> {
|
||||
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<Ori> for vek::vec::repr_simd::Vec2<f32> {
|
||||
|
@ -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<f32>);
|
||||
|
||||
impl Vel {
|
||||
pub fn zero() -> Self { Vel(Vec3::zero()) }
|
||||
}
|
||||
|
||||
impl Component for Vel {
|
||||
// TODO: why not regular vec storage????
|
||||
type Storage = IdvStorage<Self>;
|
||||
@ -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<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
/// 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<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
// 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<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Gravity(pub f32);
|
||||
|
||||
impl Component for Gravity {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[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<Vec3<f32>>,
|
||||
pub touch_entities: HashSet<Uid>,
|
||||
pub in_liquid: Option<f32>, // Depth
|
||||
pub in_fluid: Option<Fluid>,
|
||||
pub ground_vel: Vec3<f32>,
|
||||
}
|
||||
|
||||
@ -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<f32> { self.in_fluid.and_then(|fluid| fluid.depth()) }
|
||||
}
|
||||
|
||||
impl Component for PhysicsState {
|
||||
|
@ -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
|
||||
|
@ -63,7 +63,6 @@ pub enum ServerEvent {
|
||||
body: comp::Body,
|
||||
light: Option<comp::LightEmitter>,
|
||||
projectile: comp::Projectile,
|
||||
gravity: Option<comp::Gravity>,
|
||||
speed: f32,
|
||||
object: Option<comp::Object>,
|
||||
},
|
||||
|
@ -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<LightEmitter>,
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
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,
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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<LightEmitter>,
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
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,
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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::<f32>::from(update.vel.0);
|
||||
let horiz_speed_sq = horiz_vel.magnitude_squared();
|
||||
|
||||
// Move player according to movement direction vector
|
||||
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::<f32>::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
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
if data
|
||||
.physics
|
||||
.in_liquid
|
||||
.in_liquid()
|
||||
.map(|depth| depth > 0.5)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
|
@ -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<LightEmitter>,
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
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,
|
||||
});
|
||||
|
@ -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<f32> {
|
||||
/// Returns thrust force if the body type can swim, otherwise None
|
||||
pub fn swim_thrust(&self) -> Option<f32> {
|
||||
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<f32> {
|
||||
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<f32> {
|
||||
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()
|
||||
handle_orientation(data, update, efficiency);
|
||||
|
||||
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 {
|
||||
BASE_HUMANOID_AIR_ACCEL
|
||||
let fw = Vec2::from(update.ori);
|
||||
fw * data.inputs.move_dir.dot(fw).max(0.0)
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
};
|
||||
|
||||
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,
|
||||
@ -279,75 +308,118 @@ pub fn handle_forced_movement(
|
||||
+ 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;
|
||||
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);
|
||||
|
||||
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_speed *= 1.4_f32.powi(level.into());
|
||||
water_accel *= 1.4_f32.powi(level.into());
|
||||
}
|
||||
|
||||
// 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
|
||||
let dir = if data.body.can_strafe() {
|
||||
data.inputs.move_dir
|
||||
} else {
|
||||
0.0
|
||||
let fw = Vec2::from(update.ori);
|
||||
fw * data.inputs.move_dir.dot(fw).max(0.0)
|
||||
};
|
||||
|
||||
// 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
|
||||
}
|
||||
* efficiency;
|
||||
|
||||
handle_orientation(
|
||||
data,
|
||||
update,
|
||||
data.body.base_ori_rate() * if data.physics.on_ground { 0.5 } else { 0.1 },
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// 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()
|
||||
{
|
||||
(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,
|
||||
data.body.jump_impulse().unwrap() * strength,
|
||||
strength * impulse / data.mass.0,
|
||||
));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.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<f32> {
|
||||
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()
|
||||
|
@ -112,6 +112,8 @@ impl Dir {
|
||||
|
||||
pub fn back() -> Self { -Dir::new(Vec3::<f32>::unit_y()) }
|
||||
|
||||
pub fn to_horizontal(self) -> Option<Self> { Self::from_unnormalized(self.xy().into()) }
|
||||
|
||||
pub fn vec(&self) -> &Vec3<f32> { &self.0 }
|
||||
|
||||
pub fn to_vec(self) -> Vec3<f32> { self.0 }
|
||||
|
@ -199,9 +199,9 @@ impl State {
|
||||
ecs.register::<comp::Mounting>();
|
||||
ecs.register::<comp::MountState>();
|
||||
ecs.register::<comp::Mass>();
|
||||
ecs.register::<comp::Density>();
|
||||
ecs.register::<comp::Collider>();
|
||||
ecs.register::<comp::Sticky>();
|
||||
ecs.register::<comp::Gravity>();
|
||||
ecs.register::<comp::CharacterState>();
|
||||
ecs.register::<comp::Object>();
|
||||
ecs.register::<comp::Group>();
|
||||
|
@ -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,
|
||||
|
@ -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<f32>, grav: f32, damp: f32) -> Vec3<f32> {
|
||||
// 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<Vox = Block> + 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<Vox = Block> + 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(
|
||||
|
@ -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<LightEmitter>,
|
||||
projectile: Projectile,
|
||||
gravity: Option<Gravity>,
|
||||
speed: f32,
|
||||
object: Option<Object>,
|
||||
) {
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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<f32>) {
|
||||
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::<comp::Mass>()
|
||||
.get(entity)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
let inventories = state.ecs().read_storage::<Inventory>();
|
||||
let falldmg = mass.0 * vel.z.powi(2) / 200.0;
|
||||
let stats = state.ecs().read_storage::<Stats>();
|
||||
// Handle health change
|
||||
if let Some(mut health) = state.ecs().write_storage::<comp::Health>().get_mut(entity) {
|
||||
@ -495,7 +501,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
||||
// Handle poise change
|
||||
if let Some(mut poise) = state.ecs().write_storage::<comp::Poise>().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));
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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<f32>, tgt: Vec3<f32>) -> Option<Dir> {
|
||||
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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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<MountState>>,
|
||||
pub group: ReadExpect<'a, UpdateTracker<Group>>,
|
||||
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
|
||||
pub density: ReadExpect<'a, UpdateTracker<Density>>,
|
||||
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
|
||||
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
|
||||
pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
|
||||
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
|
||||
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
|
||||
pub beam_segment: ReadExpect<'a, UpdateTracker<BeamSegment>>,
|
||||
@ -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<MountState>>,
|
||||
group: WriteExpect<'a, UpdateTracker<Group>>,
|
||||
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
||||
density: WriteExpect<'a, UpdateTracker<Density>>,
|
||||
collider: WriteExpect<'a, UpdateTracker<Collider>>,
|
||||
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
||||
gravity: WriteExpect<'a, UpdateTracker<Gravity>>,
|
||||
inventory: WriteExpect<'a, UpdateTracker<Inventory>>,
|
||||
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
|
||||
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
|
||||
@ -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::<MountState>();
|
||||
world.register_tracker::<Group>();
|
||||
world.register_tracker::<Mass>();
|
||||
world.register_tracker::<Density>();
|
||||
world.register_tracker::<Collider>();
|
||||
world.register_tracker::<Sticky>();
|
||||
world.register_tracker::<Gravity>();
|
||||
world.register_tracker::<Inventory>();
|
||||
world.register_tracker::<CharacterState>();
|
||||
world.register_tracker::<Shockwave>();
|
||||
|
@ -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::<DeltaTime>().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<f32>,
|
||||
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<f32>,
|
||||
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 {
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user