Rebalanced masses, dimensions, swim thrusts, and made various improvements to water movement that help with gameplay and pathfinding. Also fixed swimming animations for many creatures.

This commit is contained in:
Joshua Barretto 2023-05-17 14:01:03 +01:00
parent 5988237b3d
commit 96bf86dae0
6 changed files with 161 additions and 137 deletions

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

View File

@ -66,8 +66,8 @@ 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(6.0, 17.0, 6.0),
Body::Galleon => Vec3::new(7.0, 32.0, 10.0),
}
}
@ -100,7 +100,7 @@ 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
_ => Density(AIR_DENSITY * 0.75 + WATER_DENSITY * 0.25), // Most boats should be buoyant
}
}

View File

@ -220,7 +220,7 @@ impl Body {
quadruped_low::Species::Mossdrake => 1.7,
_ => 2.0,
},
Body::Ship(ship) if ship.has_water_thrust() => 0.1,
Body::Ship(ship) if ship.has_water_thrust() => 0.2,
Body::Ship(_) => 0.12,
Body::Arthropod(_) => 3.5,
}
@ -228,33 +228,46 @@ impl Body {
/// 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) if ship.has_water_thrust() => 0.1 * self.mass().0,
Body::Ship(_) => return None,
Body::BipedLarge(_) => 120.0 * self.mass().0,
Body::Golem(_) => 0.5 * self.mass().0,
Body::BipedSmall(_) => 1000.0 * self.mass().0,
Body::BirdMedium(_) => 1500.0 * self.mass().0,
Body::BirdLarge(_) => 35.0 * self.mass().0,
Body::FishMedium(_) => 75.0 * self.mass().0,
Body::FishSmall(_) => 120.0 * self.mass().0,
Body::Dragon(_) => 0.5 * 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 => 700.0 * self.mass().0,
_ => 3.0 * self.mass().0,
},
Body::QuadrupedLow(_) => 120.0 * self.mass().0,
Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Mammoth => 75.0 * self.mass().0,
_ => 500.0 * self.mass().0,
},
Body::QuadrupedSmall(_) => 1500.0 * self.mass().0,
Body::Arthropod(_) => 300.0 * self.mass().0,
} * front_profile,
)
}
/// Returns thrust force if the body type can fly, otherwise None
@ -371,7 +384,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()
@ -645,19 +658,27 @@ 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
};
// 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::broadcast(data.dt.0)
* Vec3::new(dir.x, dir.y, move_z)
.try_normalized()
.unwrap_or_default()
// 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 * 3.0).clamp(0.0, 1.0);
true
} else {
@ -822,7 +843,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 get out of
&& (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 +1126,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

@ -1612,17 +1612,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 +1720,32 @@ 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)
});
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,
});
// skating (ski)
if !vel.0.xy().is_approx_zero()
&& physics_state
@ -1778,7 +1809,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);
@ -1794,32 +1837,6 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
}
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

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

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