Merge branch 'zesterer/various-fixes' into 'master'

Rebalanced masses, dimensions, swim thrusts, and made various improvements to...

See merge request veloren/veloren!3938
This commit is contained in:
Joshua Barretto 2023-05-26 11:41:50 +00:00
commit 0f25de1dfe
32 changed files with 763 additions and 337 deletions

View File

@ -70,6 +70,24 @@
offset: (-1.5, -6.0, -5.0),
central: ("empty"),
),
custom_indices: {
1: Air(ChairSingle, 4),
2: Air(Helm, 0),
3: Air(Door, 6),
4: Air(Door, 2),
9: Air(CraftingBench, 0),
10: Air(Window1, 0),
11: Air(RepairBench, 0),
12: Air(DismantlingBench, 4),
13: Air(Window1, 2),
14: Air(Crate, 0),
15: Air(Anvil, 2),
16: Air(Tent, 0),
17: Air(CookingPot, 0),
18: Air(WallLampSmall, 4),
19: Air(Lantern, 4),
},
),
Galleon: (
bone0: (
@ -90,7 +108,129 @@
),
custom_indices: {
1: Air(Helm, 0),
1: Air(ChairSingle, 4),
2: Air(Helm, 0),
3: Air(DoorWide, 4),
4: Air(DoorWide, 0),
9: Air(CraftingBench, 0),
10: Air(Window1, 0),
11: Air(RepairBench, 0),
12: Air(DismantlingBench, 4),
13: Air(Window1, 2),
14: Air(Crate, 0),
15: Air(Cauldron, 2),
16: Air(Tent, 0),
17: Air(CookingPot, 0),
18: Air(WallLamp, 4),
19: Air(Lantern, 4),
},
),
Skiff: (
bone0: (
offset: (-3.5, -7, -0.5),
central: ("skiff.structure"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
bone2: (
offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
bone3: (
offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
custom_indices: {
1: Air(ChairSingle, 4),
2: Air(Helm, 0),
3: Air(Door, 4),
4: Air(Door, 0),
9: Air(CraftingBench, 0),
10: Air(Window1, 0),
11: Air(RepairBench, 0),
12: Air(DismantlingBench, 4),
13: Air(Window1, 2),
14: Air(Crate, 0),
15: Air(Cauldron, 2),
16: Air(Tent, 0),
17: Air(CookingPot, 0),
18: Air(WallLampSmall, 4),
19: Air(Lantern, 4),
},
),
Submarine: (
bone0: (
offset: (-5.5, -18.0, -1.0),
central: ("submarine.structure"),
),
bone1: (
offset: (-3.5, -1.0, -3.5),
central: ("submarine.prop"),
),
bone2: (
offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
bone3: (
offset: (-3.5, -3.0, -3.5),
central: ("submarine.rudder"),
),
custom_indices: {
1: Air(ChairSingle, 4),
2: Air(Helm, 0),
3: Air(Door, 4),
4: Air(Door, 0),
9: Air(CraftingBench, 0),
10: Air(Window1, 0),
11: Air(RepairBench, 0),
12: Air(DismantlingBench, 4),
13: Air(Window1, 2),
14: Air(Crate, 0),
15: Air(Cauldron, 2),
16: Air(Tent, 0),
17: Air(CookingPot, 0),
18: Air(WallLampSmall, 4),
19: Air(Lantern, 4),
},
),
Carriage: (
bone0: (
offset: (-4.5, -7.0, -0.5),
central: ("carriage.structure"),
),
bone1: (
offset: (-2.0, -5.0, -2.0),
central: ("carriage.axle"),
),
bone2: (
offset: (-2.0, -5.0, -2.0),
central: ("carriage.axle"),
),
bone3: (
offset: (0.0, 0.0, 0.0),
central: ("empty"),
),
custom_indices: {
1: Air(ChairSingle, 4),
2: Air(Helm, 0),
3: Air(Door, 4),
4: Air(Door, 0),
9: Air(CraftingBench, 0),
10: Air(Window1, 0),
11: Air(RepairBench, 0),
12: Air(DismantlingBench, 4),
13: Air(Window1, 2),
14: Air(Crate, 0),
15: Air(Cauldron, 2),
16: Air(ChairSingle, 0),
17: Air(CookingPot, 0),
18: Air(WallLampSmall, 4),
19: Air(Lantern, 4),
},
),
})

BIN
assets/common/voxel/carriage/axle.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/common/voxel/carriage/structure.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/common/voxel/galleon/structure.vox (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
assets/common/voxel/skiff/structure.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/common/voxel/submarine/prop.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/common/voxel/submarine/rudder.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/common/voxel/submarine/structure.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -352,7 +352,17 @@ impl ServerChatCommand {
Some(Admin),
),
ServerChatCommand::Airship => cmd(
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
vec![
Enum(
"kind",
comp::ship::ALL_AIRSHIPS
.iter()
.map(|b| format!("{b:?}"))
.collect(),
Optional,
),
Float("destination_degrees_ccw_of_east", 90.0, Optional),
],
"Spawns an airship",
Some(Admin),
),
@ -649,7 +659,17 @@ impl ServerChatCommand {
Some(Admin),
),
ServerChatCommand::Ship => cmd(
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
vec![
Enum(
"kind",
comp::ship::ALL_SHIPS
.iter()
.map(|b| format!("{b:?}"))
.collect(),
Optional,
),
Float("destination_degrees_ccw_of_east", 90.0, Optional),
],
"Spawns a ship",
Some(Admin),
),

View File

@ -261,8 +261,7 @@ impl Body {
Body::BirdMedium(_) => 700.0,
Body::BirdLarge(_) => 2_200.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::Dragon(_) => 5_000.0,
Body::Golem(_) => WATER_DENSITY * 2.5,
Body::Humanoid(_) => HUMAN_DENSITY,
@ -312,7 +311,7 @@ impl Body {
bird_medium::Species::Puffin => 2.0,
bird_medium::Species::Toucan => 4.5,
},
Body::BirdLarge(_) => 100.0,
Body::BirdLarge(_) => 250.0,
Body::Dragon(_) => 20_000.0,
Body::FishMedium(_) => 5.0,
Body::FishSmall(_) => 1.0,
@ -337,23 +336,23 @@ impl Body {
// alligator or smaller, so whatever
quadruped_low::Species::Crocodile => 360.0,
quadruped_low::Species::SeaCrocodile => 410.0,
quadruped_low::Species::Deadwood => 150.0,
quadruped_low::Species::Deadwood => 200.0,
quadruped_low::Species::Monitor => 200.0,
quadruped_low::Species::Pangolin => 300.0,
quadruped_low::Species::Salamander => 350.0,
quadruped_low::Species::Elbst => 65.0,
quadruped_low::Species::Elbst => 350.0,
quadruped_low::Species::Tortoise => 300.0,
quadruped_low::Species::Lavadrake => 500.0,
quadruped_low::Species::Icedrake => 500.0,
quadruped_low::Species::Mossdrake => 500.0,
quadruped_low::Species::Lavadrake => 700.0,
quadruped_low::Species::Icedrake => 700.0,
quadruped_low::Species::Mossdrake => 700.0,
quadruped_low::Species::Rocksnapper => 450.0,
quadruped_low::Species::Rootsnapper => 450.0,
quadruped_low::Species::Reefsnapper => 450.0,
quadruped_low::Species::Maneater => 350.0,
quadruped_low::Species::Sandshark => 450.0,
quadruped_low::Species::Hakulaq => 300.0,
quadruped_low::Species::Dagon => 400.0,
quadruped_low::Species::Basilisk => 500.0,
quadruped_low::Species::Hakulaq => 400.0,
quadruped_low::Species::Dagon => 600.0,
quadruped_low::Species::Basilisk => 800.0,
},
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Bear => 500.0, // ~✅ (350-700 kg)
@ -361,12 +360,15 @@ impl Body {
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::Horse => 300.0, // ~✅
quadruped_medium::Species::Kelpie => 250.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,
quadruped_medium::Species::Dreadhorn => 500.0,
quadruped_medium::Species::Mammoth => 1500.0,
quadruped_medium::Species::Catoblepas => 300.0,
_ => 200.0,
},
Body::QuadrupedSmall(body) => match body.species {
@ -401,9 +403,9 @@ impl Body {
Body::Theropod(body) => match body.species {
// 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,
theropod::Species::Archaeos => 8_000.0,
theropod::Species::Ntouka => 8_000.0,
theropod::Species::Odonto => 8_000.0,
theropod::Species::Dodarock => 700.0,
theropod::Species::Sandraptor => 500.0,
theropod::Species::Snowraptor => 500.0,
@ -460,7 +462,7 @@ impl Body {
| bird_large::Species::FrostWyvern
| bird_large::Species::CloudWyvern
| bird_large::Species::SeaWyvern
| bird_large::Species::WealdWyvern => Vec3::new(4.0, 9.0, 4.5),
| bird_large::Species::WealdWyvern => Vec3::new(2.5, 9.0, 4.5),
_ => Vec3::new(2.0, 6.0, 3.5),
},
Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0),
@ -480,14 +482,14 @@ impl Body {
quadruped_medium::Species::Akhlut => Vec3::new(2.5, 7.0, 3.0),
quadruped_medium::Species::Barghest => Vec3::new(2.0, 4.4, 2.7),
quadruped_medium::Species::Bear => Vec3::new(2.0, 3.8, 3.0),
quadruped_medium::Species::Catoblepas => Vec3::new(2.0, 4.0, 2.9),
quadruped_medium::Species::Catoblepas => Vec3::new(2.0, 4.0, 2.3),
quadruped_medium::Species::Cattle => Vec3::new(2.0, 3.6, 2.4),
quadruped_medium::Species::Deer => Vec3::new(2.0, 3.0, 2.2),
quadruped_medium::Species::Dreadhorn => Vec3::new(3.5, 6.0, 4.0),
quadruped_medium::Species::Dreadhorn => Vec3::new(3.5, 6.0, 2.6),
quadruped_medium::Species::Frostfang => Vec3::new(1.5, 3.0, 1.5),
quadruped_medium::Species::Grolgar => Vec3::new(2.0, 4.0, 2.0),
quadruped_medium::Species::Highland => Vec3::new(2.0, 3.6, 2.4),
quadruped_medium::Species::Horse => Vec3::new(2.0, 3.0, 2.4),
quadruped_medium::Species::Horse => Vec3::new(1.6, 3.0, 2.4),
quadruped_medium::Species::Lion => Vec3::new(2.0, 3.3, 2.0),
quadruped_medium::Species::Moose => Vec3::new(2.0, 4.0, 2.5),
quadruped_medium::Species::Bristleback => Vec3::new(2.0, 3.0, 2.0),
@ -500,7 +502,7 @@ impl Body {
quadruped_medium::Species::Llama => Vec3::new(2.0, 2.5, 2.6),
quadruped_medium::Species::Alpaca => Vec3::new(2.0, 2.0, 2.0),
quadruped_medium::Species::Camel => Vec3::new(2.0, 4.0, 3.5),
quadruped_medium::Species::Wolf => Vec3::new(1.7, 3.0, 1.8),
quadruped_medium::Species::Wolf => Vec3::new(1.25, 3.0, 1.8),
// FIXME: We really shouldn't be doing wildcards here
_ => Vec3::new(2.0, 3.0, 2.0),
},
@ -519,8 +521,9 @@ impl Body {
quadruped_low::Species::Hakulaq => Vec3::new(1.8, 3.0, 2.0),
quadruped_low::Species::Dagon => Vec3::new(3.0, 6.0, 2.0),
quadruped_low::Species::Icedrake => Vec3::new(2.0, 5.5, 2.5),
quadruped_low::Species::Lavadrake => Vec3::new(2.0, 4.7, 2.5),
quadruped_low::Species::Maneater => Vec3::new(2.5, 3.7, 4.0),
quadruped_low::Species::Lavadrake => Vec3::new(2.0, 5.5, 2.5),
quadruped_low::Species::Mossdrake => Vec3::new(2.0, 5.5, 2.5),
quadruped_low::Species::Maneater => Vec3::new(2.0, 3.7, 4.0),
quadruped_low::Species::Monitor => Vec3::new(1.4, 3.2, 1.3),
quadruped_low::Species::Pangolin => Vec3::new(1.0, 2.6, 1.1),
quadruped_low::Species::Rocksnapper => Vec3::new(2.5, 3.5, 2.9),
@ -531,14 +534,13 @@ impl Body {
quadruped_low::Species::Salamander => Vec3::new(1.7, 4.0, 1.3),
quadruped_low::Species::Elbst => Vec3::new(1.7, 4.0, 1.3),
quadruped_low::Species::Tortoise => Vec3::new(1.7, 2.7, 1.5),
quadruped_low::Species::Mossdrake => Vec3::new(2.0, 4.7, 2.5),
_ => 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, 8.5, 8.0),
theropod::Species::Ntouka => Vec3::new(4.0, 9.0, 6.6),
theropod::Species::Odonto => Vec3::new(4.0, 8.0, 6.6),
theropod::Species::Archaeos => Vec3::new(4.5, 8.5, 8.0),
theropod::Species::Ntouka => Vec3::new(4.5, 9.0, 6.6),
theropod::Species::Odonto => Vec3::new(4.5, 8.0, 6.6),
theropod::Species::Dodarock => Vec3::new(2.0, 3.0, 2.6),
theropod::Species::Sandraptor => Vec3::new(2.0, 3.0, 2.6),
theropod::Species::Snowraptor => Vec3::new(2.0, 3.0, 2.6),
@ -1095,6 +1097,9 @@ impl Body {
ship::Body::AirBalloon => [0.0, 0.0, 5.0],
ship::Body::SailBoat => [-2.0, -5.0, 4.0],
ship::Body::Galleon => [-2.0, -5.0, 4.0],
ship::Body::Skiff => [1.0, -2.0, 2.0],
ship::Body::Submarine => [1.0, -2.0, 2.0],
ship::Body::Carriage => [1.0, -2.0, 2.0],
ship::Body::Volume => [0.0, 0.0, 0.0],
},
_ => [0.0, 0.0, 0.0],

View File

@ -9,15 +9,23 @@ use serde::{Deserialize, Serialize};
use std::sync::Arc;
use vek::*;
pub const ALL_BODIES: [Body; 4] = [
pub const ALL_BODIES: [Body; 6] = [
Body::DefaultAirship,
Body::AirBalloon,
Body::SailBoat,
Body::Galleon,
Body::Skiff,
Body::Submarine,
];
pub const ALL_AIRSHIPS: [Body; 2] = [Body::DefaultAirship, Body::AirBalloon];
pub const ALL_SHIPS: [Body; 2] = [Body::SailBoat, Body::Galleon];
pub const ALL_SHIPS: [Body; 5] = [
Body::SailBoat,
Body::Galleon,
Body::Skiff,
Body::Submarine,
Body::Carriage,
];
make_case_elim!(
body,
@ -29,6 +37,9 @@ make_case_elim!(
SailBoat = 2,
Galleon = 3,
Volume = 4,
Skiff = 5,
Submarine = 6,
Carriage = 7,
}
);
@ -58,6 +69,9 @@ impl Body {
Body::AirBalloon => Some("air_balloon.structure"),
Body::SailBoat => Some("sail_boat.structure"),
Body::Galleon => Some("galleon.structure"),
Body::Skiff => Some("skiff.structure"),
Body::Submarine => Some("submarine.structure"),
Body::Carriage => Some("carriage.structure"),
Body::Volume => None,
}
}
@ -66,8 +80,11 @@ impl Body {
match self {
Body::DefaultAirship | Body::Volume => Vec3::new(25.0, 50.0, 40.0),
Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0),
Body::SailBoat => Vec3::new(13.0, 31.0, 3.0),
Body::Galleon => Vec3::new(13.0, 32.0, 3.0),
Body::SailBoat => Vec3::new(12.0, 32.0, 6.0),
Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
Body::Submarine => Vec3::new(2.0, 15.0, 8.0),
Body::Carriage => Vec3::new(6.0, 12.0, 8.0),
}
}
@ -100,7 +117,10 @@ impl Body {
pub fn density(&self) -> Density {
match self {
Body::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY),
_ => Density(AIR_DENSITY * 0.2 + WATER_DENSITY * 0.8), // Most boats should be buoyant
Body::Submarine => Density(WATER_DENSITY), // Neutrally buoyant
Body::Carriage => Density(WATER_DENSITY * 0.5),
_ => Density(AIR_DENSITY * 0.95 + WATER_DENSITY * 0.05), /* Most boats should be very
* buoyant */
}
}
@ -113,9 +133,11 @@ impl Body {
pub fn flying_height(&self) -> f32 { if self.can_fly() { 200.0 } else { 0.0 } }
pub fn has_water_thrust(&self) -> bool {
!self.can_fly() // TODO: Differentiate this more carefully
matches!(self, Body::SailBoat | Body::Galleon | Body::Skiff)
}
pub fn has_wheels(&self) -> bool { matches!(self, Body::Carriage) }
pub fn make_collider(&self) -> Collider {
match self.manifest_entry() {
Some(manifest_entry) => Collider::Voxel {

View File

@ -211,6 +211,13 @@ impl Body {
}
}
/// Physically incorrect (but relatively dt-independent) way to calculate
/// drag coefficients for liquids.
// TODO: Remove this in favour of `aerodynamic_forces` (see: `integrate_forces`)
pub fn drag_coefficient_liquid(&self, fluid_density: f32, scale: f32) -> f32 {
fluid_density * self.parasite_drag(scale)
}
/// 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

View File

@ -153,7 +153,7 @@ impl Collider {
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
match self {
Collider::Voxel { .. } | Collider::Volume(_) => (0.0, 1.0),
Collider::Voxel { .. } | Collider::Volume(_) => (0.0, 2.0),
Collider::CapsulePrism { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
Collider::Point => (0.0, 0.0),
}

View File

@ -188,6 +188,7 @@ pub enum ServerEvent {
LandOnGround {
entity: EcsEntity,
vel: Vec3<f32>,
surface_normal: Vec3<f32>,
},
EnableLantern(EcsEntity),
DisableLantern(EcsEntity),

View File

@ -211,10 +211,11 @@ impl VolumePos {
uid_allocator: &UidAllocator,
mut read_pos_and_ori: impl FnMut(Entity) -> Option<(comp::Pos, comp::Ori)>,
colliders: &ReadStorage<comp::Collider>,
) -> Option<(Mat4<f32>, Block)> {
) -> Option<(Mat4<f32>, comp::Ori, Block)> {
match self.kind {
Volume::Terrain => Some((
Mat4::translation_3d(self.pos.as_()),
comp::Ori::default(),
*terrain.get(self.pos).ok()?,
)),
Volume::Entity(uid) => {
@ -234,7 +235,7 @@ impl VolumePos {
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
* Mat4::<f32>::translation_3d(local_translation);
Some((trans, block))
Some((trans, ori, block))
})
},
}

View File

@ -12,7 +12,7 @@ use crate::{
tool::{self, AbilityContext},
Hands, ItemKind, ToolKind,
},
quadruped_low, quadruped_medium, quadruped_small,
quadruped_low, quadruped_medium, quadruped_small, ship,
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
InventoryAction, Melee, StateUpdate,
@ -21,7 +21,7 @@ use crate::{
event::{LocalEvent, ServerEvent},
outcome::Outcome,
states::{behavior::JoinData, utils::CharacterState::Idle, *},
terrain::{TerrainGrid, UnlockKind},
terrain::{Block, TerrainGrid, UnlockKind},
util::Dir,
vol::ReadVol,
};
@ -146,6 +146,7 @@ impl Body {
quadruped_low::Species::Deadwood => 140.0,
quadruped_low::Species::Mossdrake => 100.0,
},
Body::Ship(ship::Body::Carriage) => 200.0,
Body::Ship(_) => 0.0,
Body::Arthropod(arthropod) => match arthropod.species {
arthropod::Species::Tarantula => 135.0,
@ -220,41 +221,56 @@ impl Body {
quadruped_low::Species::Mossdrake => 1.7,
_ => 2.0,
},
Body::Ship(ship) if ship.has_water_thrust() => 0.1,
Body::Ship(_) => 0.12,
Body::Ship(ship::Body::Carriage) => 0.04,
Body::Ship(ship) if ship.has_water_thrust() => 5.0 / self.dimensions().y,
Body::Ship(_) => 6.0 / self.dimensions().y,
Body::Arthropod(_) => 3.5,
}
}
/// Returns thrust force if the body type can swim, otherwise None
pub fn swim_thrust(&self) -> Option<f32> {
match self {
Body::Object(_) => None,
Body::ItemDrop(_) => None,
Body::BipedLarge(_) | Body::Golem(_) => Some(3000.0 * self.mass().0),
Body::BipedSmall(_) => Some(1000.0 * self.mass().0),
Body::BirdMedium(_) => Some(1200.0 * self.mass().0),
Body::BirdLarge(_) => Some(750.0 * self.mass().0),
Body::FishMedium(_) => Some(50.0 * self.mass().0),
Body::FishSmall(_) => Some(50.0 * self.mass().0),
Body::Dragon(_) => Some(3000.0 * self.mass().0),
Body::Humanoid(_) => Some(2500.0 * self.mass().0),
Body::Theropod(body) => match body.species {
theropod::Species::Sandraptor
| theropod::Species::Snowraptor
| theropod::Species::Sunlizard
| theropod::Species::Woodraptor
| theropod::Species::Dodarock
| theropod::Species::Yale => Some(2500.0 * self.mass().0),
_ => Some(100.0 * self.mass().0),
},
Body::QuadrupedLow(_) => Some(2500.0 * self.mass().0),
Body::QuadrupedMedium(_) => Some(3000.0 * self.mass().0),
Body::QuadrupedSmall(_) => Some(3000.0 * self.mass().0),
Body::Ship(ship) if ship.has_water_thrust() => Some(3500.0 * self.mass().0),
Body::Ship(_) => None,
Body::Arthropod(_) => Some(300.0 * self.mass().0),
}
// Swim thrust is proportional to the frontal area of the creature, since we
// assume that strength roughly scales according to square laws. Also,
// it happens to make balancing against drag much simpler.
let front_profile = self.dimensions().x * self.dimensions().z;
Some(
match self {
Body::Object(_) => return None,
Body::ItemDrop(_) => return None,
Body::Ship(ship::Body::Submarine) => 1000.0 * self.mass().0,
Body::Ship(ship) if ship.has_water_thrust() => 500.0 * self.mass().0,
Body::Ship(_) => return None,
Body::BipedLarge(_) => 120.0 * self.mass().0,
Body::Golem(_) => 100.0 * self.mass().0,
Body::BipedSmall(_) => 1000.0 * self.mass().0,
Body::BirdMedium(_) => 400.0 * self.mass().0,
Body::BirdLarge(_) => 400.0 * self.mass().0,
Body::FishMedium(_) => 200.0 * self.mass().0,
Body::FishSmall(_) => 300.0 * self.mass().0,
Body::Dragon(_) => 50.0 * self.mass().0,
// Humanoids are a bit different: we try to give them thrusts that result in similar
// speeds for gameplay reasons
Body::Humanoid(_) => 4_000_000.0 / self.mass().0,
Body::Theropod(body) => match body.species {
theropod::Species::Sandraptor
| theropod::Species::Snowraptor
| theropod::Species::Sunlizard
| theropod::Species::Woodraptor
| theropod::Species::Dodarock
| theropod::Species::Axebeak
| theropod::Species::Yale => 500.0 * self.mass().0,
_ => 150.0 * self.mass().0,
},
Body::QuadrupedLow(_) => 1200.0 * self.mass().0,
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Mammoth => 150.0 * self.mass().0,
_ => 1000.0 * self.mass().0,
},
Body::QuadrupedSmall(_) => 1500.0 * self.mass().0,
Body::Arthropod(_) => 500.0 * self.mass().0,
} * front_profile,
)
}
/// Returns thrust force if the body type can fly, otherwise None
@ -371,7 +387,7 @@ pub fn handle_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f3
&& data.body.fly_thrust().is_some()
{
fly_move(data, update, efficiency);
} else if let Some(submersion) = (data.physics.on_ground.is_none()
} else if let Some(submersion) = (data.physics.in_liquid().is_some()
&& data.body.swim_thrust().is_some())
.then_some(submersion)
.flatten()
@ -573,6 +589,29 @@ pub fn handle_orientation(
(a.to_quat().into_vec4() - b.to_quat().into_vec4()).reduce(|a, b| a.abs() + b.abs())
}
let (tilt_ori, efficiency) = if let Body::Ship(ship) = data.body && ship.has_wheels() {
let height_at = |rpos| data
.terrain
.ray(
data.pos.0 + rpos + Vec3::unit_z() * 4.0,
data.pos.0 + rpos - Vec3::unit_z() * 4.0,
)
.until(Block::is_solid)
.cast()
.0;
// Do some cheap raycasting with the ground to determine the appropriate orientation for the vehicle
let x_diff = (height_at(data.ori.to_horizontal().right().to_vec() * 3.0) - height_at(data.ori.to_horizontal().right().to_vec() * -3.0)) / 10.0;
let y_diff = (height_at(data.ori.to_horizontal().look_dir().to_vec() * -4.5) - height_at(data.ori.to_horizontal().look_dir().to_vec() * 4.5)) / 10.0;
(
Quaternion::rotation_y(x_diff.atan()) * Quaternion::rotation_x(y_diff.atan()),
(data.vel.0 - data.physics.ground_vel).xy().magnitude().max(3.0) * efficiency,
)
} else {
(Quaternion::identity(), efficiency)
};
// Direction is set to the override if one is provided, else if entity is
// strafing or attacking the horiontal component of the look direction is used,
// else the current horizontal movement direction is used
@ -587,7 +626,8 @@ pub fn handle_orientation(
} else {
Dir::from_unnormalized(data.inputs.move_dir.into())
.map_or_else(|| to_horizontal_fast(data.ori), |dir| dir.into())
};
}
.rotated(tilt_ori);
// unit is multiples of 180°
let half_turns_per_tick = data.body.base_ori_rate() / data.scale.map_or(1.0, |s| s.0.sqrt())
* efficiency
@ -602,7 +642,9 @@ pub fn handle_orientation(
// very rough guess
let ticks_from_target_guess = ori_absdiff(&update.ori, &target_ori) / half_turns_per_tick;
let instantaneous = ticks_from_target_guess < 1.0;
update.ori = if instantaneous {
update.ori = if data.volume_mount_data.is_some() {
update.ori
} else if instantaneous {
target_ori
} else {
let target_fraction = {
@ -645,19 +687,28 @@ fn swim_move(
fw * data.inputs.move_dir.dot(fw).max(0.0)
};
// Autoswim to stay afloat
let move_z = if submersion < 1.0 && data.inputs.move_z.abs() < f32::EPSILON {
(submersion - 0.1).max(0.0)
// Automatically tread water to stay afloat
let move_z = if submersion < 1.0
&& data.inputs.move_z.abs() < f32::EPSILON
&& data.physics.on_ground.is_none()
{
submersion.max(0.0) * 0.1
} 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()
// Assume that feet/flippers get less efficient as we become less submerged
let move_z = move_z.min((submersion * 1.5 - 0.5).clamp(0.0, 1.0).powi(2));
update.vel.0 += Vec3::new(dir.x, dir.y, move_z)
// TODO: Should probably be normalised, but creates odd discrepancies when treading water
// .try_normalized()
// .unwrap_or_default()
* water_accel
* (submersion - 0.2).clamp(0.0, 1.0).powi(2);
// Gives a good balance between submerged and surface speed
* submersion.clamp(0.0, 1.0).sqrt()
// Good approximate compensation for dt-dependent effects
* data.dt.0 * 0.04;
true
} else {
@ -822,7 +873,8 @@ pub fn handle_climb(data: &JoinData<'_>, update: &mut StateUpdate) -> bool {
.map(|depth| depth > 1.0)
.unwrap_or(false)
//&& update.vel.0.z < 0.0
&& data.body.can_climb()
// *All* entities can climb when in liquids, to let them climb out near the surface
&& (data.body.can_climb() || data.physics.in_liquid().is_some())
&& update.energy.current() > 1.0
{
update.character = CharacterState::Climb(climb::Data::create_adjusted_by_skills(data));
@ -1104,14 +1156,16 @@ pub fn handle_jump(
.then(|| data.body.jump_impulse())
.flatten()
.and_then(|impulse| {
if data.physics.on_ground.is_some() {
if data.physics.in_liquid().is_some() {
if data.physics.on_wall.is_some() {
// Allow entities to make a small jump when at the edge of a body of water,
// allowing them to path out of it
Some(impulse * 0.75)
} else {
None
}
} else if data.physics.on_ground.is_some() {
Some(impulse)
} else if data.physics.in_liquid().map_or(false, |h| h < 1.0)
&& data.physics.on_wall.is_some()
{
// Allow entities to make a small jump when at the edge of a body of water,
// allowing them to path out of it
Some(impulse * 0.75)
} else {
None
}

View File

@ -103,7 +103,7 @@ impl<'a> System<'a> for Sys {
// For each volume rider.
for (entity, is_volume_rider) in (&entities, &is_volume_riders).join() {
if let Some((mut mat, _)) = is_volume_rider.pos.get_block_and_transform(
if let Some((mut mat, volume_ori, _)) = is_volume_rider.pos.get_block_and_transform(
&terrain,
&uid_allocator,
|e| positions.get(e).copied().zip(orientations.get(e).copied()),
@ -114,21 +114,27 @@ impl<'a> System<'a> for Sys {
continue;
};
if let Some(ori) = is_volume_rider.block.get_ori() {
let mount_block_ori = if let Some(ori) = is_volume_rider.block.get_ori() {
mat *= Mat4::identity()
.translated_3d(mount_offset)
.rotated_z(std::f32::consts::PI * 0.25 * ori as f32)
.translated_3d(Vec3::new(0.5, 0.5, 0.0));
ori
} else {
mat *= Mat4::identity().translated_3d(mount_offset + Vec3::new(0.5, 0.5, 0.0));
}
0
};
if let Some(pos) = positions.get_mut(entity) {
pos.0 = mat.mul_point(Vec3::zero());
}
if let Some(ori) = orientations.get_mut(entity) {
*ori = Ori::from_unnormalized_vec(mat.mul_direction(mount_dir))
.unwrap_or_default();
*ori = volume_ori.rotated(
Ori::from_unnormalized_vec(mount_dir)
.unwrap_or_default()
.to_quat()
.rotated_z(std::f32::consts::PI * 0.25 * mount_block_ori as f32),
);
}
}
let v = match is_volume_rider.pos.kind {

View File

@ -11,7 +11,7 @@ use common::{
link::Is,
mounting::{Rider, VolumeRider},
outcome::Outcome,
resources::DeltaTime,
resources::{DeltaTime, GameMode},
states,
terrain::{Block, BlockKind, TerrainGrid},
uid::Uid,
@ -62,29 +62,48 @@ fn integrate_forces(
// 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,
wings,
scale.map_or(1.0, |s| s.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 {
// Multiply by a factor to prevent full stop,
// as this can cause things to get stuck in high-density medium
vel.0 -= vel.0.projected(&impulse) * 0.9;
} else {
vel.0 = new_v;
// HACK: We should really use the latter logic (i.e: `aerodynamic_forces`) for
// liquids, but it results in pretty serious dt-dependent problems that
// are extremely difficult to resolve. This is a compromise: for liquids
// only, we calculate drag using an incorrect (but still visually plausible)
// model that is much more resistant to differences in dt. Because it's
// physically incorrect anyway, there are magic coefficients that
// exist simply to get us closer to what water 'should' feel like.
if fluid.is_liquid() {
let fric = body
.drag_coefficient_liquid(fluid_density.0, scale.map_or(1.0, |s| s.0))
.powf(0.75)
* 0.02;
let fvel = fluid.flow_vel();
// Drag is relative to fluid velocity, so compensate before applying drag
vel.0 = (vel.0 - fvel.0) * (1.0 / (1.0 + fric)).powf(dt.0 * 10.0) + fvel.0;
} else {
let impulse = dt.0
* body.aerodynamic_forces(
&rel_flow,
fluid_density.0,
wings,
scale.map_or(1.0, |s| s.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 {
// Multiply by a factor to prevent full stop,
// as this can cause things to get stuck in high-density medium
vel.0 -= vel.0.projected(&impulse) * 0.9;
} else {
vel.0 = new_v;
}
}
};
}
debug_assert!(!vel.0.map(|a| a.is_nan()).reduce_or());
};
@ -116,6 +135,7 @@ pub struct PhysicsRead<'a> {
terrain: ReadExpect<'a, TerrainGrid>,
dt: Read<'a, DeltaTime>,
event_bus: Read<'a, EventBus<ServerEvent>>,
game_mode: ReadExpect<'a, GameMode>,
scales: ReadStorage<'a, Scale>,
stickies: ReadStorage<'a, Sticky>,
immovables: ReadStorage<'a, Immovable>,
@ -277,7 +297,6 @@ impl<'a> PhysicsData<'a> {
Collider::Voxel { .. } | Collider::Volume(_) | Collider::Point => None,
};
phys_cache.origins = origins;
phys_cache.ori = ori;
}
}
@ -651,6 +670,12 @@ impl<'a> PhysicsData<'a> {
if in_loaded_chunk
// And not already stuck on a block (e.g., for arrows)
&& !(physics_state.on_surface().is_some() && sticky.is_some())
// HACK: Special-case boats. Experimentally, clients are *bad* at making guesses about movement,
// and this is a particular problem for volume entities since careful control of velocity is
// required for nice movement of entities on top of the volume. Special-case volume entities here
// to prevent weird drag/gravity guesses messing with movement, relying on the client's hermite
// interpolation instead.
&& !(matches!(body, Body::Ship(_)) && matches!(&*read.game_mode, GameMode::Client))
{
// Clamp dt to an effective 10 TPS, to prevent gravity
// from slamming the players into the floor when
@ -708,11 +733,10 @@ impl<'a> PhysicsData<'a> {
// Update cached 'old' physics values to the current values ready for the next
// tick
prof_span!(guard, "record ori into phys_cache");
for (pos, ori, previous_phys_cache, _) in (
for (pos, ori, previous_phys_cache) in (
&write.positions,
&write.orientations,
&mut write.previous_phys_cache,
&read.colliders,
)
.join()
{
@ -861,12 +885,13 @@ impl<'a> PhysicsData<'a> {
tgt_pos,
&mut vel,
physics_state,
Vec3::zero(),
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
|entity, vel, surface_normal| {
land_on_ground = Some((entity, vel, surface_normal))
},
read,
&ori,
);
@ -894,12 +919,13 @@ impl<'a> PhysicsData<'a> {
tgt_pos,
&mut vel,
physics_state,
Vec3::zero(),
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
|entity, vel, surface_normal| {
land_on_ground = Some((entity, vel, surface_normal))
},
read,
&ori,
);
@ -1095,88 +1121,128 @@ impl<'a> PhysicsData<'a> {
}
let mut physics_state_delta = physics_state.clone();
// deliberately don't use scale yet here, because the
// 11.0/0.8 thing is
// in the comp::Scale for visual reasons
let mut cpos = *pos;
let wpos = cpos.0;
// TODO: Cache the matrices here to avoid recomputing
// Helper function for computing a transformation matrix and its
// inverse. Should
// be much cheaper than using `Mat4::inverted`.
let from_to_matricies =
|entity_rpos: Vec3<f32>, collider_ori: Quaternion<f32>| {
(
Mat4::<f32>::translation_3d(entity_rpos)
* Mat4::from(collider_ori)
* Mat4::scaling_3d(previous_cache_other.scale)
* Mat4::translation_3d(
voxel_collider.translation,
),
Mat4::<f32>::translation_3d(
-voxel_collider.translation,
) * Mat4::scaling_3d(
1.0 / previous_cache_other.scale,
) * Mat4::from(collider_ori.inverse())
* Mat4::translation_3d(-entity_rpos),
)
};
let transform_last_from =
Mat4::<f32>::translation_3d(
// Compute matrices that allow us to transform to and from the
// coordinate space of
// the collider. We have two variants of each, one for the
// current state and one for
// the previous state. This allows us to 'perfectly' track
// change in position
// between ticks, which prevents entities falling through voxel
// colliders due to spurious
// issues like differences in ping/variable dt.
// TODO: Cache the matrices here to avoid recomputing for each
// entity on them
let (_transform_last_from, transform_last_to) =
from_to_matricies(
previous_cache_other.pos.unwrap_or(*pos_other).0
- previous_cache.pos.unwrap_or(Pos(wpos)).0,
) * Mat4::from(previous_cache_other.ori)
* Mat4::<f32>::scaling_3d(previous_cache_other.scale)
* Mat4::<f32>::translation_3d(
voxel_collider.translation,
);
let transform_last_to = transform_last_from.inverted();
- previous_cache.pos.unwrap_or(*pos).0,
previous_cache_other.ori,
);
let (transform_from, transform_to) =
from_to_matricies(pos_other.0 - pos.0, ori_other.to_quat());
let transform_from =
Mat4::<f32>::translation_3d(pos_other.0 - wpos)
* Mat4::from(ori_other.to_quat())
* Mat4::<f32>::scaling_3d(previous_cache_other.scale)
* Mat4::<f32>::translation_3d(
voxel_collider.translation,
);
let transform_to = transform_from.inverted();
// Compute the velocity of the collider, accounting for changes
// in orientation
// from the last tick. We then model this change as a change in
// surface velocity
// for the collider.
let vel_other = {
let pos_rel =
(Mat4::<f32>::translation_3d(
-voxel_collider.translation,
) * Mat4::from(ori_other.to_quat().inverse()))
.mul_point(pos.0 - pos_other.0);
let rpos_last =
(Mat4::<f32>::from(previous_cache_other.ori)
* Mat4::translation_3d(voxel_collider.translation))
.mul_point(pos_rel);
vel_other.0
+ (pos.0 - (pos_other.0 + rpos_last)) / read.dt.0
};
let ori_last_from = Mat4::from(previous_cache_other.ori);
let ori_last_to = ori_last_from.inverted();
{
// Transform the entity attributes into the coordinate space
// of the collider ready
// for collision resolution
let mut rpos =
Pos(transform_last_to.mul_point(Vec3::zero()));
vel.0 = previous_cache_other.ori.inverse()
* (vel.0 - vel_other);
let ori_from = Mat4::from(ori_other.to_quat());
// Perform collision resolution
box_voxel_collision(
(radius, z_min, z_max),
&voxel_collider.volume(),
entity,
&mut rpos,
transform_to.mul_point(tgt_pos - pos.0),
&mut vel,
&mut physics_state_delta,
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel, surface_normal| {
land_on_ground = Some((
entity,
Vel(previous_cache_other.ori * vel.0
+ vel_other),
previous_cache_other.ori * surface_normal,
));
},
read,
&ori,
);
// The velocity of the collider, taking into account
// orientation.
let pos_rel = (Mat4::<f32>::translation_3d(Vec3::zero())
* Mat4::from(ori_other.to_quat())
* Mat4::<f32>::translation_3d(voxel_collider.translation))
.inverted()
.mul_point(wpos - pos_other.0);
let rpos_last = (Mat4::<f32>::translation_3d(Vec3::zero())
* Mat4::from(previous_cache_other.ori)
* Mat4::<f32>::translation_3d(voxel_collider.translation))
.mul_point(pos_rel);
let vel_other = vel_other.0
+ (wpos - (pos_other.0 + rpos_last)) / read.dt.0;
// Transform entity attributes back into world space now
// that we've performed
// collision resolution with them
tgt_pos = transform_from.mul_point(rpos.0) + pos.0;
vel.0 = previous_cache_other.ori * vel.0 + vel_other;
}
cpos.0 = transform_last_to.mul_point(Vec3::zero());
vel.0 = ori_last_to.mul_direction(vel.0 - vel_other);
let cylinder = (radius, z_min, z_max);
box_voxel_collision(
cylinder,
&voxel_collider.volume(),
entity,
&mut cpos,
transform_to.mul_point(tgt_pos - wpos),
&mut vel,
&mut physics_state_delta,
ori_last_to.mul_direction(vel_other),
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel| {
land_on_ground =
Some((entity, Vel(ori_from.mul_direction(vel.0))));
},
read,
&ori,
);
cpos.0 = transform_from.mul_point(cpos.0) + wpos;
vel.0 = ori_from.mul_direction(vel.0) + vel_other;
tgt_pos = cpos.0;
// union in the state updates, so that the state isn't just
// based on the most
// recent terrain that collision was attempted with
// Collision resolution may also change the physics state. Since
// we may be interacting
// with multiple colliders at once (along with the regular
// terrain!) we keep track
// of a physics state 'delta' and try to sensibly resolve them
// against one-another at each step.
if physics_state_delta.on_ground.is_some() {
physics_state.ground_vel = vel_other;
// Rotate if on ground
// TODO: Do we need to do this? Perhaps just take the
// ground_vel regardless?
physics_state.ground_vel = previous_cache_other.ori
* physics_state_delta.ground_vel
+ vel_other;
}
if physics_state_delta.on_surface().is_some() {
// If the collision resulted in us being on a surface,
// rotate us with the
// collider. Really this should be modelled via friction or
// something, but
// our physics model doesn't really take orientation into
// consideration.
ori = ori.rotated(
ori_other.to_quat()
* previous_cache_other.ori.inverse(),
@ -1188,7 +1254,7 @@ impl<'a> PhysicsData<'a> {
physics_state.on_wall = physics_state.on_wall.or_else(|| {
physics_state_delta
.on_wall
.map(|dir| ori_from.mul_direction(dir))
.map(|dir| previous_cache_other.ori * dir)
});
physics_state.in_fluid = match (
physics_state.in_fluid,
@ -1277,9 +1343,15 @@ impl<'a> PhysicsData<'a> {
drop(guard);
let mut event_emitter = read.event_bus.emitter();
land_on_grounds.into_iter().for_each(|(entity, vel)| {
event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 });
});
land_on_grounds
.into_iter()
.for_each(|(entity, vel, surface_normal)| {
event_emitter.emit(ServerEvent::LandOnGround {
entity,
vel: vel.0,
surface_normal,
});
});
}
fn update_cached_spatial_grid(&mut self) {
@ -1354,12 +1426,11 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
tgt_pos: Vec3<f32>,
vel: &mut Vel,
physics_state: &mut PhysicsState,
ground_vel: Vec3<f32>,
dt: &DeltaTime,
was_on_ground: bool,
block_snap: bool,
climbing: bool,
mut land_on_ground: impl FnMut(Entity, Vel),
mut land_on_ground: impl FnMut(Entity, Vel, Vec3<f32>),
read: &PhysicsRead,
ori: &Ori,
) {
@ -1530,14 +1601,14 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
/* if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { */
if resolve_dir.z > 0.0 {
on_ground = Some(block);
if !was_on_ground {
land_on_ground(entity, *vel);
}
} else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 {
on_ceiling = true;
}
if resolve_dir.magnitude_squared() > 0.0 {
land_on_ground(entity, *vel, resolve_dir.normalized());
}
// When the resolution direction is non-vertical, we must be colliding
// with a wall
//
@ -1612,17 +1683,22 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
if on_ground.is_some() {
physics_state.on_ground = on_ground;
// If the space below us is free, then "snap" to the ground
} else if vel.0.z <= 0.0 && was_on_ground && block_snap && {
//prof_span!("snap check");
collision_with(
pos.0 - Vec3::unit_z() * 1.1,
&terrain,
near_aabb,
radius,
z_range.clone(),
vel.0,
)
} {
} else if vel.0.z <= 0.0
&& was_on_ground
&& block_snap
&& physics_state.in_liquid().is_none()
&& {
//prof_span!("snap check");
collision_with(
pos.0 - Vec3::unit_z() * 1.1,
&terrain,
near_aabb,
radius,
z_range.clone(),
vel.0,
)
}
{
//prof_span!("snap!!");
let snap_height = terrain
.get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32))
@ -1715,6 +1791,39 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
physics_state.on_wall = on_wall;
let fric_mod = read.stats.get(entity).map_or(1.0, |s| s.friction_modifier);
physics_state.in_fluid = liquid
.map(|(kind, max_z)| {
// NOTE: assumes min_z == 0.0
let depth = max_z - pos.0.z;
// 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
let new_depth = physics_state.in_liquid().map_or(depth, |old_depth| {
(old_depth + old_pos.z - pos.0.z).max(depth)
});
// TODO: Change this at some point to allow entities to be moved by liquids?
let vel = Vel::zero();
if depth > 0.0 {
physics_state.ground_vel = vel.0;
}
Fluid::Liquid {
kind,
depth: new_depth,
vel,
}
})
.or_else(|| match physics_state.in_fluid {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
}),
fluid => fluid,
});
// skating (ski)
if !vel.0.xy().is_approx_zero()
&& physics_state
@ -1778,7 +1887,19 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
physics_state.skating_active = true;
vel.0 = Vec3::new(new_ground_speed.x, new_ground_speed.y, 0.0);
} else {
let ground_fric = physics_state
let ground_fric = if physics_state.in_liquid().is_some() {
// HACK:
// If we're in a liquid, radically reduce ground friction (i.e: assume that
// contact force is negligible due to buoyancy) Note that this might
// not be realistic for very dense entities (currently no entities in Veloren
// are sufficiently negatively buoyant for this to matter). We
// should really make friction be proportional to net downward force, but
// that means taking into account buoyancy which is a bit difficult to do here
// for now.
0.1
} else {
1.0
} * physics_state
.on_ground
.map(|b| b.get_friction())
.unwrap_or(0.0);
@ -1790,36 +1911,10 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
let fric = ground_fric.max(wall_fric);
if fric > 0.0 {
vel.0 *= (1.0 - fric.min(1.0) * fric_mod).powf(dt.0 * 60.0);
physics_state.ground_vel = ground_vel;
physics_state.ground_vel = Vec3::zero();
}
physics_state.skating_active = false;
}
physics_state.in_fluid = liquid
.map(|(kind, max_z)| {
// NOTE: assumes min_z == 0.0
let depth = max_z - pos.0.z;
// 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
let new_depth = physics_state.in_liquid().map_or(depth, |old_depth| {
(old_depth + old_pos.z - pos.0.z).max(depth)
});
Fluid::Liquid {
kind,
depth: new_depth,
vel: Vel::zero(),
}
})
.or_else(|| match physics_state.in_fluid {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
}),
fluid => fluid,
});
}
fn voxel_collider_bounding_sphere(

View File

@ -313,6 +313,8 @@ impl Vehicle {
comp::ship::Body::AirBalloon => 8.0,
comp::ship::Body::SailBoat => 5.0,
comp::ship::Body::Galleon => 6.0,
comp::ship::Body::Skiff => 6.0,
comp::ship::Body::Submarine => 4.0,
_ => 10.0,
}
}

View File

@ -167,6 +167,9 @@ impl<'a> AgentData<'a> {
&& self.physics_state.on_wall.is_some();
self.jump_if(bearing.z > 1.5 || climbing_out_of_water, controller);
controller.inputs.move_z = bearing.z;
if bearing.z > 0.0 {
controller.inputs.climb = Some(comp::Climb::Up);
}
}
pub fn jump_if(&self, condition: bool, controller: &mut Controller) {
@ -274,7 +277,6 @@ impl<'a> AgentData<'a> {
) {
self.traverse(controller, bearing, speed.min(speed_factor));
self.jump_if(self.traversal_config.can_fly, controller);
controller.inputs.climb = Some(comp::Climb::Up);
let height_offset = bearing.z
+ if self.traversal_config.can_fly {

View File

@ -230,26 +230,28 @@ fn position_mut<T>(
dismount_volume: Option<bool>,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> CmdResult<T> {
let entity = if dismount_volume.unwrap_or(true) {
if dismount_volume.unwrap_or(true) {
server
.state
.ecs()
.write_storage::<Is<VolumeRider>>()
.remove(entity);
entity
} else {
server
.state
.read_storage::<Is<Rider>>()
.get(entity)
.and_then(|is_rider| {
server
.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(is_rider.mount.into())
})
.or(server
}
let entity = server
.state
.read_storage::<Is<Rider>>()
.get(entity)
.and_then(|is_rider| {
server
.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(is_rider.mount.into())
})
.map(Ok)
.or_else(|| {
server
.state
.read_storage::<Is<VolumeRider>>()
.get(entity)
@ -265,9 +267,8 @@ fn position_mut<T>(
.retrieve_entity_internal(uid.into())?),
})
})
.transpose()?)
.unwrap_or(entity)
};
})
.unwrap_or(Ok(entity))?;
let mut maybe_pos = None;
@ -1638,25 +1639,25 @@ fn handle_spawn_airship(
args: Vec<String>,
_action: &ServerChatCommand,
) -> CmdResult<()> {
let angle = parse_cmd_args!(args, f32);
let (body_name, angle) = parse_cmd_args!(args, String, f32);
let mut pos = position(server, target, "target")?;
pos.0.z += 50.0;
const DESTINATION_RADIUS: f32 = 2000.0;
let angle = angle.map(|a| a * std::f32::consts::PI / 180.0);
let dir = angle.map(|a| Vec3::new(a.cos(), a.sin(), 0.0));
let destination = dir.map(|dir| pos.0 + dir * DESTINATION_RADIUS + Vec3::new(0.0, 0.0, 200.0));
let mut rng = thread_rng();
let ship = comp::ship::Body::random_airship_with(&mut rng);
let ship = if let Some(body_name) = body_name {
*comp::ship::ALL_AIRSHIPS
.iter()
.find(|body| format!("{body:?}") == body_name)
.ok_or_else(|| format!("No such airship '{body_name}'."))?
} else {
comp::ship::Body::random_airship_with(&mut thread_rng())
};
let ori = comp::Ori::from(common::util::Dir::new(dir.unwrap_or(Vec3::unit_y())));
let mut builder = server
.state
.create_ship(pos, ori, ship, |ship| ship.make_collider())
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
flicker: 1.0,
animated: true,
});
.create_ship(pos, ori, ship, |ship| ship.make_collider());
if let Some(pos) = destination {
let (kp, ki, kd) =
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));
@ -1682,25 +1683,25 @@ fn handle_spawn_ship(
args: Vec<String>,
_action: &ServerChatCommand,
) -> CmdResult<()> {
let angle = parse_cmd_args!(args, f32);
let (body_name, angle) = parse_cmd_args!(args, String, f32);
let mut pos = position(server, target, "target")?;
pos.0.z += 50.0;
pos.0.z += 2.0;
const DESTINATION_RADIUS: f32 = 2000.0;
let angle = angle.map(|a| a * std::f32::consts::PI / 180.0);
let dir = angle.map(|a| Vec3::new(a.cos(), a.sin(), 0.0));
let destination = dir.map(|dir| pos.0 + dir * DESTINATION_RADIUS + Vec3::new(0.0, 0.0, 200.0));
let mut rng = thread_rng();
let ship = comp::ship::Body::random_ship_with(&mut rng);
let ship = if let Some(body_name) = body_name {
*comp::ship::ALL_SHIPS
.iter()
.find(|body| format!("{body:?}") == body_name)
.ok_or_else(|| format!("No such airship '{body_name}'."))?
} else {
comp::ship::Body::random_airship_with(&mut thread_rng())
};
let ori = comp::Ori::from(common::util::Dir::new(dir.unwrap_or(Vec3::unit_y())));
let mut builder = server
.state
.create_ship(pos, ori, ship, |ship| ship.make_collider())
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
flicker: 1.0,
animated: true,
});
.create_ship(pos, ori, ship, |ship| ship.make_collider());
if let Some(pos) = destination {
let (kp, ki, kd) =
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));

View File

@ -595,14 +595,21 @@ pub fn handle_delete(server: &mut Server, entity: EcsEntity) {
.map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity"));
}
pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>) {
pub fn handle_land_on_ground(
server: &Server,
entity: EcsEntity,
vel: Vec3<f32>,
surface_normal: Vec3<f32>,
) {
let ecs = server.state.ecs();
let relative_vel = vel.dot(-surface_normal);
// The second part of this if statement disables all fall damage when in the
// water. This was added as a *temporary* fix a bug that causes you to take
// fall damage while swimming downwards. FIXME: Fix the actual bug and
// remove the following relevant part of the if statement.
if vel.z <= -30.0
if relative_vel >= 30.0
&& ecs
.read_storage::<PhysicsState>()
.get(entity)
@ -610,10 +617,10 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
{
let char_states = ecs.read_storage::<CharacterState>();
let reduced_vel_z = if let Some(CharacterState::DiveMelee(c)) = char_states.get(entity) {
(vel.z + c.static_data.vertical_speed).min(0.0)
let reduced_vel = if let Some(CharacterState::DiveMelee(c)) = char_states.get(entity) {
(relative_vel + c.static_data.vertical_speed).min(0.0)
} else {
vel.z
relative_vel
};
let mass = ecs
@ -621,7 +628,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
.get(entity)
.copied()
.unwrap_or_default();
let impact_energy = mass.0 * reduced_vel_z.powi(2) / 2.0;
let impact_energy = mass.0 * reduced_vel.powi(2) / 2.0;
let falldmg = impact_energy / 1000.0;
let inventories = ecs.read_storage::<Inventory>();
@ -655,7 +662,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
server_eventbus.emit_now(ServerEvent::HealthChange { entity, change });
// Emit poise change
let poise_damage = -(mass.0 * reduced_vel_z.powi(2) / 1500.0);
let poise_damage = -(mass.0 * reduced_vel.powi(2) / 1500.0);
let poise_change = Poise::apply_poise_reduction(
poise_damage,
inventories.get(entity),

View File

@ -158,7 +158,7 @@ pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: Vo
&state.read_storage(),
);
if let Some((mat, block)) = block_transform
if let Some((mat, _, block)) = block_transform
&& let Some(mount_offset) = block.mount_offset() {
let mount_pos = (mat * mount_offset.0.with_w(1.0)).xyz();
let within_range = {

View File

@ -801,7 +801,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
},
&state.read_storage(),
)
.map(|(mat, _)| mat.mul_point(Vec3::broadcast(0.5)))
.map(|(mat, _, _)| mat.mul_point(Vec3::broadcast(0.5)))
});
if !in_range {
debug!(

View File

@ -120,9 +120,11 @@ impl Server {
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
ServerEvent::GroupManip(entity, manip) => handle_group(self, entity, manip),
ServerEvent::Respawn(entity) => handle_respawn(self, entity),
ServerEvent::LandOnGround { entity, vel } => {
handle_land_on_ground(self, entity, vel)
},
ServerEvent::LandOnGround {
entity,
vel,
surface_normal,
} => handle_land_on_ground(self, entity, vel, surface_normal),
ServerEvent::EnableLantern(entity) => handle_lantern(self, entity, true),
ServerEvent::DisableLantern(entity) => handle_lantern(self, entity, false),
ServerEvent::NpcInteract(interactor, target, subject) => {

View File

@ -114,6 +114,7 @@ pub enum TrailSource {
Weapon,
GliderLeft,
GliderRight,
Propeller(f32),
}
impl TrailSource {
@ -147,6 +148,10 @@ impl TrailSource {
Vec4::new(-GLIDER_HORIZ, 0.0, GLIDER_VERT, 1.0),
Vec4::new(-(GLIDER_HORIZ + GLIDER_WIDTH), 0.0, GLIDER_VERT, 1.0),
),
Self::Propeller(length) => (
Vec4::new(0.0, 0.0, *length * 0.5, 1.0),
Vec4::new(0.0, 0.0, *length, 1.0),
),
}
}
}

View File

@ -45,10 +45,12 @@ impl Animation for IdleAnimation {
next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2);
next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2);
next.bone1.orientation = Quaternion::rotation_y(acc_vel * 0.8);
next.bone1.orientation = Quaternion::rotation_z(s_a.bone1_ori)
* Quaternion::rotation_y(acc_vel * s_a.bone_rotation_rate);
next.bone2.position = Vec3::new(s_a.bone2.0, s_a.bone2.1, s_a.bone2.2);
next.bone2.orientation = Quaternion::rotation_y(-acc_vel * 0.8);
next.bone2.orientation = Quaternion::rotation_z(s_a.bone2_ori)
* Quaternion::rotation_y(-acc_vel * s_a.bone_rotation_rate);
next.bone3.position = Vec3::new(s_a.bone3.0, s_a.bone3.1, s_a.bone3.2);
next.bone3.orientation = Quaternion::rotation_z(tilt * 25.0);

View File

@ -3,7 +3,7 @@ pub mod idle;
// Reexports
pub use self::idle::IdleAnimation;
use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton};
use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton, TrailSource};
use common::comp::{self};
use core::convert::TryFrom;
@ -34,12 +34,16 @@ impl Skeleton for ShipSkeleton {
// Ships are normal scale
let scale_mat = Mat4::scaling_3d(1.0);
let attr = SkeletonAttr::from(&body);
let bone0_mat = base_mat * scale_mat * Mat4::<f32>::from(self.bone0);
let bone1_mat = bone0_mat * Mat4::<f32>::from(self.bone1);
let bone2_mat = bone0_mat * Mat4::<f32>::from(self.bone2);
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
make_bone(bone0_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone1)),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone2)),
make_bone(bone1_mat),
make_bone(bone2_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone3)),
];
Offsets {
@ -51,8 +55,12 @@ impl Skeleton for ShipSkeleton {
.mul_point(comp::Body::Ship(body).mount_offset().into_tuple().into()),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
primary_trail_mat: attr
.bone1_prop_trail_offset
.map(|offset| (bone1_mat, TrailSource::Propeller(offset))),
secondary_trail_mat: attr
.bone2_prop_trail_offset
.map(|offset| (bone2_mat, TrailSource::Propeller(offset))),
}
}
}
@ -62,6 +70,11 @@ pub struct SkeletonAttr {
bone1: (f32, f32, f32),
bone2: (f32, f32, f32),
bone3: (f32, f32, f32),
bone1_ori: f32,
bone2_ori: f32,
bone_rotation_rate: f32,
bone1_prop_trail_offset: Option<f32>,
bone2_prop_trail_offset: Option<f32>,
}
impl<'a> TryFrom<&'a comp::Body> for SkeletonAttr {
@ -82,6 +95,11 @@ impl Default for SkeletonAttr {
bone1: (0.0, 0.0, 0.0),
bone2: (0.0, 0.0, 0.0),
bone3: (0.0, 0.0, 0.0),
bone1_ori: 0.0,
bone2_ori: 0.0,
bone_rotation_rate: 0.0,
bone1_prop_trail_offset: None,
bone2_prop_trail_offset: None,
}
}
}
@ -95,6 +113,9 @@ impl<'a> From<&'a Body> for SkeletonAttr {
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Skiff => (0.0, 0.0, 0.0),
Submarine => (0.0, 0.0, 0.0),
Carriage => (0.0, 0.0, 0.0),
Volume => (0.0, 0.0, 0.0),
},
bone1: match body {
@ -102,6 +123,9 @@ impl<'a> From<&'a Body> for SkeletonAttr {
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Skiff => (0.0, 0.0, 0.0),
Submarine => (0.0, -15.0, 3.5),
Carriage => (0.0, 3.0, 2.0),
Volume => (0.0, 0.0, 0.0),
},
bone2: match body {
@ -109,6 +133,9 @@ impl<'a> From<&'a Body> for SkeletonAttr {
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Skiff => (0.0, 0.0, 0.0),
Submarine => (0.0, 0.0, 0.0),
Carriage => (0.0, -3.0, 2.0),
Volume => (0.0, 0.0, 0.0),
},
bone3: match body {
@ -116,8 +143,32 @@ impl<'a> From<&'a Body> for SkeletonAttr {
AirBalloon => (0.0, -9.0, 8.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Skiff => (0.0, 0.0, 0.0),
Submarine => (0.0, -18.0, 3.5),
Carriage => (0.0, 0.0, 0.0),
Volume => (0.0, 0.0, 0.0),
},
bone1_ori: match body {
Carriage => std::f32::consts::PI * 0.5,
_ => 0.0,
},
bone2_ori: match body {
Carriage => std::f32::consts::PI * -0.5,
_ => 0.0,
},
bone_rotation_rate: match body {
Carriage => 0.25,
_ => 0.8,
},
bone1_prop_trail_offset: match body {
DefaultAirship => Some(8.5),
Submarine => Some(3.5),
_ => None,
},
bone2_prop_trail_offset: match body {
DefaultAirship => Some(8.5),
_ => None,
},
}
}
}

View File

@ -2042,7 +2042,7 @@ impl Hud {
// Render overtime for an interactable block
if let Some(Interactable::Block(block, pos, interaction)) = interactable
&& let Some((mat, _)) = pos.get_block_and_transform(
&& let Some((mat, _, _)) = pos.get_block_and_transform(
&ecs.read_resource(),
&ecs.read_resource(),
|e| ecs.read_storage::<vcomp::Interpolated>().get(e).map(|interpolated| (comp::Pos(interpolated.pos), interpolated.ori)),
@ -4751,7 +4751,7 @@ impl Hud {
},
&client.state().read_storage(),
)
.map_or(false, |(mat, block)| {
.map_or(false, |(mat, _, block)| {
block.get_sprite() == Some(*sprite)
&& mat.mul_point(Vec3::broadcast(0.5)).distance(player_pos)
< MAX_PICKUP_RANGE

View File

@ -2275,8 +2275,8 @@ impl FigureMgr {
skeleton_attr,
)
},
// Running
(false, _, true) => anim::quadruped_small::RunAnimation::update_skeleton(
// Swimming
(_, _, true) => anim::quadruped_small::RunAnimation::update_skeleton(
&QuadrupedSmallSkeleton::default(),
(
rel_vel.magnitude(),
@ -2307,13 +2307,6 @@ impl FigureMgr {
&mut state_animation_rate,
skeleton_attr,
),
_ => anim::quadruped_small::IdleAnimation::update_skeleton(
&QuadrupedSmallSkeleton::default(),
time,
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
};
let target_bones = match &character {
CharacterState::ComboMelee(s) => {
@ -2476,7 +2469,7 @@ impl FigureMgr {
)
},
//Swimming
(false, _, true) => anim::quadruped_medium::RunAnimation::update_skeleton(
(_, _, true) => anim::quadruped_medium::RunAnimation::update_skeleton(
&QuadrupedMediumSkeleton::default(),
(
rel_vel.magnitude(),
@ -2501,13 +2494,6 @@ impl FigureMgr {
skeleton_attr,
)
},
_ => anim::quadruped_medium::IdleAnimation::update_skeleton(
&QuadrupedMediumSkeleton::default(),
time,
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
};
let target_bones = match &character {
CharacterState::BasicMelee(s) => {
@ -2851,7 +2837,7 @@ impl FigureMgr {
skeleton_attr,
),
// Swimming
(false, _, true) => anim::quadruped_low::RunAnimation::update_skeleton(
(_, _, true) => anim::quadruped_low::RunAnimation::update_skeleton(
&QuadrupedLowSkeleton::default(),
(
rel_vel.magnitude(),
@ -2882,13 +2868,6 @@ impl FigureMgr {
&mut state_animation_rate,
skeleton_attr,
),
_ => anim::quadruped_low::IdleAnimation::update_skeleton(
&QuadrupedLowSkeleton::default(),
time,
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
};
let target_bones = match &character {
CharacterState::BasicRanged(s) => {

View File

@ -587,7 +587,13 @@ impl Scene {
let is_running = ecs
.read_storage::<comp::Vel>()
.get(scene_data.viewpoint_entity)
.map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2))
.zip(
ecs.read_storage::<comp::PhysicsState>()
.get(scene_data.viewpoint_entity),
)
.map(|(v, ps)| {
(v.0 - ps.ground_vel).magnitude_squared() > RUNNING_THRESHOLD.powi(2)
})
.unwrap_or(false);
let on_ground = ecs