diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 8112fc1419..5f1510437f 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -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), diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index da1b494148..680938276c 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -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 } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 197fb62bbc..ca714281df 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -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 { - 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 } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 7a1fc28b4c..d3ded17a6f 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1612,17 +1612,22 @@ fn box_voxel_collision + 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 + 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 + 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 + 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( diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 8e78c9df4d..a969890945 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -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 { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 6593575630..77cb3691dc 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -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) => {