mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
0f25de1dfe
@ -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
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
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)
BIN
assets/common/voxel/galleon/structure.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/common/voxel/sail_boat/structure.vox
(Stored with Git LFS)
BIN
assets/common/voxel/sail_boat/structure.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/common/voxel/skiff/structure.vox
(Stored with Git LFS)
Normal file
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
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
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
BIN
assets/common/voxel/submarine/structure.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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),
|
||||
),
|
||||
|
@ -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],
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -188,6 +188,7 @@ pub enum ServerEvent {
|
||||
LandOnGround {
|
||||
entity: EcsEntity,
|
||||
vel: Vec3<f32>,
|
||||
surface_normal: Vec3<f32>,
|
||||
},
|
||||
EnableLantern(EcsEntity),
|
||||
DisableLantern(EcsEntity),
|
||||
|
@ -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))
|
||||
})
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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),
|
||||
|
@ -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 = {
|
||||
|
@ -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!(
|
||||
|
@ -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) => {
|
||||
|
@ -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),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user