Basic fluid dynamics and physical properties for entities

This commit is contained in:
Ludvig Böklin 2021-03-23 10:51:53 +01:00
parent bcf9a8089f
commit 762c68cfbb
51 changed files with 1121 additions and 574 deletions

View File

@ -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

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -9,6 +9,5 @@ BasicRanged(
),
projectile_body: Object(Arrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
projectile_speed: 100.0,
)

View File

@ -8,6 +8,5 @@ BasicRanged(
col: (0.0, 1.0, 0.33).into(),
..Default::default()
}),*/
projectile_gravity: None,
projectile_speed: 100.0,
)

View File

@ -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,
)

View File

@ -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,

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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"] }

View File

@ -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)

View File

@ -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)

View File

@ -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,
},

View File

@ -16,6 +16,7 @@ pub mod theropod;
use crate::{
assets::{self, Asset},
consts::{HUMAN_DENSITY, WATER_DENSITY},
make_case_elim,
npc::NpcKind,
};
@ -24,7 +25,7 @@ use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use vek::*;
use super::BuffKind;
use super::{BuffKind, Density, Mass};
make_case_elim!(
body,
@ -145,165 +146,228 @@ impl<
impl Body {
pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) }
// Note: this might need to be refined to something more complex for realistic
// behavior with less cylindrical bodies (e.g. wolfs)
#[allow(unreachable_patterns)]
pub fn radius(&self) -> f32 {
// TODO: Improve these values (some might be reliant on more info in inner type)
match self {
Body::Humanoid(humanoid) => match (humanoid.species, humanoid.body_type) {
(humanoid::Species::Orc, humanoid::BodyType::Male) => 0.75,
(humanoid::Species::Orc, humanoid::BodyType::Female) => 0.75,
(humanoid::Species::Human, humanoid::BodyType::Male) => 0.75,
(humanoid::Species::Human, humanoid::BodyType::Female) => 0.75,
(humanoid::Species::Elf, humanoid::BodyType::Male) => 0.75,
(humanoid::Species::Elf, humanoid::BodyType::Female) => 0.75,
(humanoid::Species::Dwarf, humanoid::BodyType::Male) => 0.75,
(humanoid::Species::Dwarf, humanoid::BodyType::Female) => 0.75,
(humanoid::Species::Undead, humanoid::BodyType::Male) => 0.75,
(humanoid::Species::Undead, humanoid::BodyType::Female) => 0.75,
(humanoid::Species::Danari, humanoid::BodyType::Male) => 0.75,
(humanoid::Species::Danari, humanoid::BodyType::Female) => 0.75,
_ => 0.75,
},
Body::QuadrupedSmall(_) => 0.6,
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Grolgar => 2.0,
quadruped_medium::Species::Tarasque => 2.0,
quadruped_medium::Species::Lion => 2.0,
quadruped_medium::Species::Saber => 2.0,
quadruped_medium::Species::Catoblepas => 2.0,
quadruped_medium::Species::Horse => 1.5,
quadruped_medium::Species::Deer => 1.5,
quadruped_medium::Species::Donkey => 1.5,
quadruped_medium::Species::Kelpie => 1.5,
quadruped_medium::Species::Barghest => 1.8,
quadruped_medium::Species::Cattle => 1.8,
quadruped_medium::Species::Highland => 1.8,
quadruped_medium::Species::Yak => 1.8,
quadruped_medium::Species::Panda => 1.8,
quadruped_medium::Species::Bear => 1.8,
_ => 1.5,
},
Body::QuadrupedLow(body) => match body.species {
quadruped_low::Species::Asp => 2.5,
quadruped_low::Species::Monitor => 2.3,
quadruped_low::Species::Crocodile => 2.4,
quadruped_low::Species::Salamander => 2.4,
quadruped_low::Species::Pangolin => 2.0,
quadruped_low::Species::Lavadrake => 2.5,
quadruped_low::Species::Deadwood => 0.5,
_ => 1.6,
},
Body::Theropod(body) => match body.species {
theropod::Species::Snowraptor => 1.5,
theropod::Species::Sandraptor => 1.5,
theropod::Species::Woodraptor => 1.5,
theropod::Species::Archaeos => 3.5,
theropod::Species::Odonto => 3.5,
theropod::Species::Yale => 0.8,
theropod::Species::Ntouka => 3.0,
_ => 1.8,
},
Body::BirdMedium(_) => 1.0,
Body::FishMedium(_) => 1.0,
Body::Dragon(_) => 8.0,
Body::BirdSmall(_) => 0.6,
Body::FishSmall(_) => 0.6,
Body::BipedLarge(body) => match body.species {
biped_large::Species::Slysaurok => 2.0,
biped_large::Species::Occultsaurok => 2.0,
biped_large::Species::Mightysaurok => 2.0,
biped_large::Species::Mindflayer => 2.2,
biped_large::Species::Minotaur => 3.0,
/// Average density of the body
// Units are based on kg/m³
pub fn density(&self) -> Density {
let d = match self {
// based on a house sparrow (Passer domesticus)
Body::BirdMedium(_) => 700.0,
Body::BirdSmall(_) => 700.0,
_ => 2.3,
},
Body::Golem(_) => 2.5,
Body::BipedSmall(_) => 0.75,
Body::Object(_) => 0.4,
Body::Ship(_) => 1.0,
}
// based on its mass divided by the volume of a bird scaled up to the size of the dragon
Body::Dragon(_) => 3_700.0,
Body::Golem(_) => WATER_DENSITY * 2.5,
Body::Humanoid(_) => HUMAN_DENSITY,
Body::Ship(ship) => ship.density().0,
Body::Object(object) => object.density().0,
_ => HUMAN_DENSITY,
};
Density(d)
}
pub fn height(&self) -> f32 {
match self {
Body::Humanoid(humanoid) => match (humanoid.species, humanoid.body_type) {
(humanoid::Species::Orc, humanoid::BodyType::Male) => 2.3,
(humanoid::Species::Orc, humanoid::BodyType::Female) => 2.2,
(humanoid::Species::Human, humanoid::BodyType::Male) => 2.3,
(humanoid::Species::Human, humanoid::BodyType::Female) => 2.2,
(humanoid::Species::Elf, humanoid::BodyType::Male) => 2.3,
(humanoid::Species::Elf, humanoid::BodyType::Female) => 2.2,
(humanoid::Species::Dwarf, humanoid::BodyType::Male) => 1.9,
(humanoid::Species::Dwarf, humanoid::BodyType::Female) => 1.8,
(humanoid::Species::Undead, humanoid::BodyType::Male) => 2.2,
(humanoid::Species::Undead, humanoid::BodyType::Female) => 2.1,
(humanoid::Species::Danari, humanoid::BodyType::Male) => 1.5,
(humanoid::Species::Danari, humanoid::BodyType::Female) => 1.4,
// Values marked with ~✅ are checked based on their RL equivalent.
// Discrepancy in size compared to their RL equivalents has not necessarily been
// taken into account.
pub fn mass(&self) -> Mass {
let m = match self {
Body::BipedLarge(body) => match body.species {
biped_large::Species::Slysaurok => 400.0,
biped_large::Species::Occultsaurok => 400.0,
biped_large::Species::Mightysaurok => 400.0,
biped_large::Species::Mindflayer => 420.0,
biped_large::Species::Minotaur => 500.0,
_ => 400.0,
},
Body::BipedSmall(_) => 50.0,
// ravens are 0.69-2 kg, crows are 0.51 kg on average
Body::BirdMedium(_) => 1.0,
// australian magpies are around 0.22-0.35 kg
Body::BirdSmall(_) => 0.3,
Body::Dragon(_) => 20_000.0,
Body::FishMedium(_) => 2.5,
Body::FishSmall(_) => 1.0,
Body::Golem(_) => 10_000.0,
Body::Humanoid(humanoid) => {
// humanoids are quite a bit larger than in real life, so we multiply their mass
// to scale it up proportionally (remember cube law)
1.0 * match (humanoid.species, humanoid.body_type) {
(humanoid::Species::Orc, humanoid::BodyType::Male) => 120.0,
(humanoid::Species::Orc, humanoid::BodyType::Female) => 120.0,
(humanoid::Species::Human, humanoid::BodyType::Male) => 77.0, // ~✅
(humanoid::Species::Human, humanoid::BodyType::Female) => 59.0, // ~✅
(humanoid::Species::Elf, humanoid::BodyType::Male) => 77.0,
(humanoid::Species::Elf, humanoid::BodyType::Female) => 59.0,
(humanoid::Species::Dwarf, humanoid::BodyType::Male) => 70.0,
(humanoid::Species::Dwarf, humanoid::BodyType::Female) => 70.0,
(humanoid::Species::Undead, humanoid::BodyType::Male) => 70.0,
(humanoid::Species::Undead, humanoid::BodyType::Female) => 50.0,
(humanoid::Species::Danari, humanoid::BodyType::Male) => 80.0,
(humanoid::Species::Danari, humanoid::BodyType::Female) => 60.0,
}
},
Body::Object(obj) => obj.mass().0,
Body::QuadrupedLow(body) => match body.species {
quadruped_low::Species::Alligator => 360.0, // ~✅
quadruped_low::Species::Asp => 300.0,
// saltwater crocodiles can weigh around 1 ton, but our version is the size of an
// alligator or smaller, so whatever
quadruped_low::Species::Crocodile => 360.0,
quadruped_low::Species::Deadwood => 400.0,
quadruped_low::Species::Lavadrake => 500.0,
quadruped_low::Species::Monitor => 100.0,
quadruped_low::Species::Pangolin => 100.0,
quadruped_low::Species::Salamander => 65.0,
quadruped_low::Species::Tortoise => 200.0,
_ => 200.0,
},
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Bear => 500.0, // ~✅ (350-700 kg)
quadruped_medium::Species::Cattle => 575.0, // ~✅ (500-650 kg)
quadruped_medium::Species::Deer => 80.0,
quadruped_medium::Species::Donkey => 200.0,
quadruped_medium::Species::Highland => 200.0,
quadruped_medium::Species::Horse => 500.0, // ~✅
quadruped_medium::Species::Kelpie => 200.0,
quadruped_medium::Species::Lion => 170.0, // ~✅ (110-225 kg)
quadruped_medium::Species::Panda => 200.0,
quadruped_medium::Species::Saber => 130.0,
quadruped_medium::Species::Yak => 200.0,
_ => 200.0,
},
Body::QuadrupedSmall(body) => match body.species {
quadruped_small::Species::Dodarock => 1.5,
quadruped_small::Species::Holladon => 1.5,
quadruped_small::Species::Truffler => 2.0,
_ => 1.0,
},
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Tarasque => 2.6,
quadruped_medium::Species::Lion => 2.0,
quadruped_medium::Species::Saber => 2.0,
quadruped_medium::Species::Catoblepas => 2.9,
quadruped_medium::Species::Barghest => 2.5,
quadruped_medium::Species::Dreadhorn => 2.5,
quadruped_medium::Species::Moose => 2.5,
_ => 1.6,
},
Body::QuadrupedLow(body) => match body.species {
quadruped_low::Species::Monitor => 1.5,
quadruped_low::Species::Tortoise => 2.0,
quadruped_low::Species::Rocksnapper => 2.9,
quadruped_low::Species::Maneater => 4.0,
_ => 1.3,
quadruped_small::Species::Batfox => 50.0,
quadruped_small::Species::Boar => 80.0, // ~✅ (60-100 kg)
quadruped_small::Species::Dodarock => 150.0,
quadruped_small::Species::Holladon => 150.0,
quadruped_small::Species::Hyena => 70.0, // ~✅ (vaguely)
quadruped_small::Species::Truffler => 150.0,
_ => 80.0,
},
Body::Theropod(body) => match body.species {
theropod::Species::Snowraptor => 2.6,
theropod::Species::Sandraptor => 2.6,
theropod::Species::Woodraptor => 2.6,
theropod::Species::Sunlizard => 2.5,
theropod::Species::Yale => 3.0,
_ => 8.0,
},
Body::BirdMedium(body) => match body.species {
bird_medium::Species::Cockatrice => 1.8,
_ => 1.1,
},
Body::FishMedium(_) => 0.8,
Body::Dragon(_) => 16.0,
Body::BirdSmall(_) => 1.1,
Body::FishSmall(_) => 0.6,
Body::BipedLarge(body) => match body.species {
biped_large::Species::Slysaurok => 3.4,
biped_large::Species::Occultsaurok => 3.4,
biped_large::Species::Mightysaurok => 3.4,
biped_large::Species::Mindflayer => 8.0,
biped_large::Species::Minotaur => 8.0,
biped_large::Species::Dullahan => 5.5,
biped_large::Species::Cyclops => 6.5,
biped_large::Species::Werewolf => 3.5,
// for reference, elephants are in the range of 2.6-6.9 tons
// and Tyrannosaurus rex were ~8.4-14 tons
theropod::Species::Archaeos => 13_000.0,
theropod::Species::Ntouka => 13_000.0,
theropod::Species::Odonto => 13_000.0,
_ => 6.0,
theropod::Species::Sandraptor => 500.0,
theropod::Species::Snowraptor => 500.0,
theropod::Species::Sunlizard => 500.0,
theropod::Species::Woodraptor => 500.0,
theropod::Species::Yale => 1_000.0,
},
Body::Golem(_) => 5.0,
Body::BipedSmall(_) => 1.4,
Body::Object(object) => match object {
object::Body::Crossbow => 1.7,
object::Body::TrainingDummy => 2.2,
_ => 1.0,
Body::Ship(ship) => ship.mass().0,
};
Mass(m)
}
/// The width (shoulder to shoulder), length (nose to tail) and height
/// respectively
pub fn dimensions(&self) -> Vec3<f32> {
match self {
Body::BipedLarge(body) => match body.species {
biped_large::Species::Cyclops => Vec3::new(4.6, 3.0, 6.5),
biped_large::Species::Dullahan => Vec3::new(4.6, 3.0, 5.5),
biped_large::Species::Mightysaurok => Vec3::new(4.0, 3.0, 3.4),
biped_large::Species::Mindflayer => Vec3::new(4.4, 3.0, 8.0),
biped_large::Species::Minotaur => Vec3::new(6.0, 3.0, 8.0),
biped_large::Species::Occultsaurok => Vec3::new(4.0, 3.0, 3.4),
biped_large::Species::Slysaurok => Vec3::new(4.0, 3.0, 3.4),
biped_large::Species::Werewolf => Vec3::new(4.0, 3.0, 3.5),
_ => Vec3::new(4.6, 3.0, 6.0),
},
Body::BipedSmall(_) => Vec3::new(1.0, 0.75, 1.4),
Body::BirdMedium(body) => match body.species {
bird_medium::Species::Cockatrice => Vec3::new(2.0, 1.0, 1.8),
_ => Vec3::new(2.0, 1.0, 1.1),
},
Body::BirdSmall(_) => Vec3::new(1.2, 0.6, 1.1),
Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0),
Body::FishMedium(_) => Vec3::new(0.5, 2.0, 0.8),
Body::FishSmall(_) => Vec3::new(0.3, 1.2, 0.6),
Body::Golem(_) => Vec3::new(5.0, 5.0, 5.0),
Body::Humanoid(humanoid) => {
let height = match (humanoid.species, humanoid.body_type) {
(humanoid::Species::Orc, humanoid::BodyType::Male) => 2.3,
(humanoid::Species::Orc, humanoid::BodyType::Female) => 2.2,
(humanoid::Species::Human, humanoid::BodyType::Male) => 2.3,
(humanoid::Species::Human, humanoid::BodyType::Female) => 2.2,
(humanoid::Species::Elf, humanoid::BodyType::Male) => 2.3,
(humanoid::Species::Elf, humanoid::BodyType::Female) => 2.2,
(humanoid::Species::Dwarf, humanoid::BodyType::Male) => 1.9,
(humanoid::Species::Dwarf, humanoid::BodyType::Female) => 1.8,
(humanoid::Species::Undead, humanoid::BodyType::Male) => 2.2,
(humanoid::Species::Undead, humanoid::BodyType::Female) => 2.1,
(humanoid::Species::Danari, humanoid::BodyType::Male) => 1.5,
(humanoid::Species::Danari, humanoid::BodyType::Female) => 1.4,
};
Vec3::new(1.5, 0.5, height)
},
Body::Object(object) => object.dimensions(),
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Barghest => Vec3::new(2.0, 3.6, 2.5),
quadruped_medium::Species::Bear => Vec3::new(2.0, 3.6, 2.0),
quadruped_medium::Species::Catoblepas => Vec3::new(2.0, 4.0, 2.9),
quadruped_medium::Species::Cattle => Vec3::new(2.0, 3.6, 2.0),
quadruped_medium::Species::Deer => Vec3::new(2.0, 3.0, 2.0),
quadruped_medium::Species::Dreadhorn => Vec3::new(2.0, 3.0, 2.5),
quadruped_medium::Species::Grolgar => Vec3::new(2.0, 4.0, 2.0),
quadruped_medium::Species::Highland => Vec3::new(2.0, 3.6, 2.0),
quadruped_medium::Species::Horse => Vec3::new(2.0, 3.0, 2.0),
quadruped_medium::Species::Lion => Vec3::new(2.0, 3.0, 2.0),
quadruped_medium::Species::Moose => Vec3::new(2.0, 4.0, 2.5),
quadruped_medium::Species::Saber => Vec3::new(2.0, 4.0, 2.0),
quadruped_medium::Species::Tarasque => Vec3::new(2.0, 4.0, 2.6),
quadruped_medium::Species::Yak => Vec3::new(2.0, 3.6, 2.0),
_ => Vec3::new(2.0, 3.0, 2.0),
},
Body::QuadrupedSmall(body) => match body.species {
quadruped_small::Species::Dodarock => Vec3::new(1.2, 1.2, 1.5),
quadruped_small::Species::Holladon => Vec3::new(1.2, 1.2, 1.5),
quadruped_small::Species::Truffler => Vec3::new(1.2, 1.2, 2.0),
_ => Vec3::new(1.2, 1.2, 1.0),
},
Body::QuadrupedLow(body) => match body.species {
quadruped_low::Species::Asp => Vec3::new(1.0, 2.5, 1.3),
quadruped_low::Species::Crocodile => Vec3::new(1.0, 2.4, 1.3),
quadruped_low::Species::Deadwood => Vec3::new(1.0, 0.5, 1.3),
quadruped_low::Species::Lavadrake => Vec3::new(1.0, 2.5, 1.3),
quadruped_low::Species::Maneater => Vec3::new(1.0, 1.6, 4.0),
quadruped_low::Species::Monitor => Vec3::new(1.0, 2.3, 1.5),
quadruped_low::Species::Pangolin => Vec3::new(1.0, 2.0, 1.3),
quadruped_low::Species::Rocksnapper => Vec3::new(1.0, 1.6, 2.9),
quadruped_low::Species::Salamander => Vec3::new(1.0, 2.4, 1.3),
quadruped_low::Species::Tortoise => Vec3::new(1.0, 1.6, 2.0),
_ => Vec3::new(1.0, 1.6, 1.3),
},
Body::Ship(ship) => ship.dimensions(),
Body::Theropod(body) => match body.species {
theropod::Species::Archaeos => Vec3::new(4.0, 7.0, 8.0),
theropod::Species::Ntouka => Vec3::new(4.0, 6.0, 8.0),
theropod::Species::Odonto => Vec3::new(4.0, 6.5, 8.0),
theropod::Species::Sandraptor => Vec3::new(2.0, 3.0, 2.6),
theropod::Species::Snowraptor => Vec3::new(2.0, 3.0, 2.6),
theropod::Species::Sunlizard => Vec3::new(2.0, 3.6, 2.5),
theropod::Species::Woodraptor => Vec3::new(2.0, 3.0, 2.6),
theropod::Species::Yale => Vec3::new(1.5, 3.2, 6.0),
},
Body::Ship(_) => 1.0,
}
}
// Note: This is used for collisions, but it's not very accurate for shapes that
// are very much not cylindrical. Eventually this ought to be replaced by more
// accurate collision shapes.
pub fn radius(&self) -> f32 {
let dim = self.dimensions();
dim.x.max(dim.y) / 2.0
}
pub fn height(&self) -> f32 { self.dimensions().z }
pub fn base_energy(&self) -> u32 {
match self {
Body::BipedLarge(biped_large) => match biped_large.species {

View File

@ -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),
}
}
}

View File

@ -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

View File

@ -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(),

View 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
*/

View File

@ -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,

View File

@ -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> {

View File

@ -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 {

View File

@ -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

View File

@ -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>,
},

View File

@ -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,
});

View File

@ -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,

View File

@ -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,

View File

@ -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;
};

View File

@ -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
update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL;
if horiz_speed_sq < GLIDE_MAX_SPEED.powi(2) {
update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL;
}
// Determine orientation vector from movement direction vector
let horiz_vel = Vec2::<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
}

View File

@ -32,7 +32,7 @@ impl CharacterBehavior for Data {
}
if data
.physics
.in_liquid
.in_liquid()
.map(|depth| depth > 0.5)
.unwrap_or(false)
{

View File

@ -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,
});

View File

@ -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()
} else {
BASE_HUMANOID_AIR_ACCEL
};
handle_orientation(data, update, efficiency);
update.vel.0 =
update.vel.0 + Vec2::broadcast(data.dt.0) * data.inputs.move_dir * accel * efficiency;
handle_orientation(data, update, data.body.base_ori_rate());
if let Some(accel) = data
.physics
.on_ground
.then_some(data.body.base_accel() * efficiency)
{
// Should ability to backpedal be separate from ability to strafe?
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* if data.body.can_strafe() {
data.inputs.move_dir
} else {
let fw = Vec2::from(update.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
};
}
}
/// Handles forced movement
@ -238,17 +269,15 @@ pub fn handle_forced_movement(
movement: ForcedMovement,
efficiency: f32,
) {
handle_orientation(data, update, efficiency);
match movement {
ForcedMovement::Forward { strength } => {
let accel = if data.physics.on_ground {
data.body.base_accel()
} else {
BASE_HUMANOID_AIR_ACCEL
};
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength);
if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) {
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength);
}
},
ForcedMovement::Leap {
vertical,
@ -268,86 +297,129 @@ pub fn handle_forced_movement(
// Apply direction
+ Vec3::from(dir)
// Multiply by forward leap strength
* forward
* forward
// Control forward movement based on look direction.
// This allows players to stop moving forward when they
// look downward at target
* (1.0 - data.inputs.look_dir.z.abs());
* (1.0 - data.inputs.look_dir.z.abs());
},
ForcedMovement::Hover { move_input } => {
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0)
+ move_input * data.inputs.move_dir.try_normalized().unwrap_or_default();
},
}
handle_orientation(data, update, data.body.base_ori_rate() * efficiency);
}
pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32) {
// Set direction based on move direction
let ori_dir = if (update.character.is_aimed() && data.body.can_strafe())
|| update.character.is_attack()
pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
let strafe_aim = update.character.is_aimed() && data.body.can_strafe();
if let Some(dir) = (strafe_aim || update.character.is_attack())
.then(|| data.inputs.look_dir.to_horizontal().unwrap_or_default())
.or_else(|| Dir::from_unnormalized(data.inputs.move_dir.into()))
{
data.inputs.look_dir.xy()
} else if !data.inputs.move_dir.is_approx_zero() {
data.inputs.move_dir
} else {
update.ori.into()
let rate = {
let angle = update.ori.look_dir().angle_between(*dir);
data.body.base_ori_rate() * efficiency * std::f32::consts::PI / angle
};
update.ori = update
.ori
.slerped_towards(dir.into(), (data.dt.0 * rate).min(0.1));
};
// Smooth orientation
update.ori = Dir::slerp_to_vec3(update.ori.look_dir(), ori_dir.into(), rate * data.dt.0).into();
}
/// Updates components to move player as if theyre swimming
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) {
let mut water_accel = BASE_HUMANOID_WATER_ACCEL;
let mut water_speed = BASE_HUMANOID_WATER_SPEED;
if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
water_speed *= 1.4_f32.powi(level.into());
water_accel *= 1.4_f32.powi(level.into());
}
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submersion: f32) -> bool {
if let Some(force) = data.body.swim_thrust() {
handle_orientation(data, update, efficiency * 0.2);
// Update velocity
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
* if update.vel.0.magnitude_squared() < water_speed.powi(2) {
water_accel
} else {
0.0
let force = efficiency * force;
let mut water_accel = force / data.mass.0;
if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
water_accel *= 1.4_f32.powi(level.into());
}
* efficiency;
handle_orientation(
data,
update,
data.body.base_ori_rate() * if data.physics.on_ground { 0.5 } else { 0.1 },
);
let dir = if data.body.can_strafe() {
data.inputs.move_dir
} else {
let fw = Vec2::from(update.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
};
// Swim
update.vel.0.z = (update.vel.0.z
+ data.dt.0
* GRAVITY
* 4.0
* data
.inputs
.move_z
.clamped(-1.0, depth.clamped(0.0, 1.0).powi(3)))
.min(water_speed);
// Autoswim to stay afloat
let move_z = if submersion < 1.0 && data.inputs.move_z.abs() < std::f32::EPSILON {
(submersion - 0.1).max(0.0)
} else {
data.inputs.move_z
};
update.vel.0 += Vec3::broadcast(data.dt.0)
* Vec3::new(dir.x, dir.y, move_z)
.try_normalized()
.unwrap_or_default()
* water_accel
* (submersion - 0.2).clamp(0.0, 1.0).powi(2);
true
} else {
false
}
}
/// Updates components to move entity as if it's flying
fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
// Update velocity (counteract gravity with lift)
update.vel.0 += Vec3::unit_z() * data.dt.0 * GRAVITY
+ Vec3::new(
data.inputs.move_dir.x,
data.inputs.move_dir.y,
data.inputs.move_z,
) * data.dt.0
* BASE_FLIGHT_ACCEL
* efficiency;
pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool {
if let Some(force) = data.body.fly_thrust() {
let thrust = efficiency * force;
handle_orientation(data, update, data.body.base_ori_rate());
let accel = thrust / data.mass.0;
handle_orientation(data, update, efficiency);
// Elevation control
match data.body {
// flappy flappy
Body::Dragon(_) | Body::BirdMedium(_) | Body::BirdSmall(_) => {
let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0));
update.vel.0.z += data.dt.0 * (anti_grav + accel * data.inputs.move_z.max(0.0));
},
// floaty floaty
Body::Ship(ship @ ship::Body::DefaultAirship) => {
let regulate_density = |min: f32, max: f32, def: f32, rate: f32| -> Density {
// Reset to default on no input
let change = if data.inputs.move_z.abs() > std::f32::EPSILON {
-data.inputs.move_z
} else {
(def - data.density.0).max(-1.0).min(1.0)
};
Density((update.density.0 + data.dt.0 * rate * change).clamp(min, max))
};
let def_density = ship.density().0;
if data.physics.in_liquid().is_some() {
let hull_density = ship.hull_density().0;
update.density.0 =
regulate_density(def_density * 0.6, hull_density, hull_density, 25.0).0;
} else {
update.density.0 =
regulate_density(def_density * 0.5, def_density * 1.5, def_density, 0.5).0;
};
},
// oopsie woopsie
// TODO: refactor to make this state impossible
_ => {},
};
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* if data.body.can_strafe() {
data.inputs.move_dir
} else {
let fw = Vec2::from(update.ori);
fw * data.inputs.move_dir.dot(fw).max(0.0)
};
true
} else {
false
}
}
/// Checks if an input related to an attack is held. If one is, moves entity
@ -408,7 +480,7 @@ pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) {
&& !data.physics.on_ground
&& !data
.physics
.in_liquid
.in_liquid()
.map(|depth| depth > 1.0)
.unwrap_or(false)
//&& update.vel.0.z < 0.0
@ -442,7 +514,7 @@ pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) {
if data.inventory.equipped(EquipSlot::Glider).is_some()
&& !data
.physics
.in_liquid
.in_liquid()
.map(|depth| depth > 1.0)
.unwrap_or(false)
&& data.body.is_humanoid()
@ -453,23 +525,16 @@ pub fn attempt_glide_wield(data: &JoinData, update: &mut StateUpdate) {
/// Checks that player can jump and sends jump event if so
pub fn handle_jump(data: &JoinData, update: &mut StateUpdate, strength: f32) -> bool {
if input_is_pressed(data, InputKind::Jump)
&& data.physics.on_ground
&& !data
.physics
.in_liquid
.map(|depth| depth > 1.0)
.unwrap_or(false)
&& data.body.jump_impulse().is_some()
{
update.local_events.push_front(LocalEvent::Jump(
data.entity,
data.body.jump_impulse().unwrap() * strength,
));
true
} else {
false
}
(input_is_pressed(data, InputKind::Jump) && data.physics.on_ground)
.then(|| data.body.jump_impulse())
.flatten()
.map(|impulse| {
update.local_events.push_front(LocalEvent::Jump(
data.entity,
strength * impulse / data.mass.0,
));
})
.is_some()
}
fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
@ -666,7 +731,12 @@ impl MovementDirection {
pub fn get_2d_dir(self, data: &JoinData) -> Vec2<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()

View File

@ -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 }

View File

@ -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>();

View File

@ -31,4 +31,4 @@ slab = "0.4.2"
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control", "derive"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" }
# Tweak running code
#inline_tweak = { version = "1.0.8", features = ["release_tweak"] }
# inline_tweak = { version = "1.0.8", features = ["release_tweak"] }

View File

@ -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,

View File

@ -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(

View File

@ -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)
}

View File

@ -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));

View File

@ -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,

View File

@ -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))

View File

@ -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)
}

View File

@ -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,

View File

@ -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>();

View File

@ -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 {

View File

@ -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(