diff --git a/common/src/comp/body/bird_medium.rs b/common/src/comp/body/bird_medium.rs
index 7754c4cce3..6b279cf536 100644
--- a/common/src/comp/body/bird_medium.rs
+++ b/common/src/comp/body/bird_medium.rs
@@ -1,6 +1,7 @@
use crate::{make_case_elim, make_proj_elim};
use rand::{seq::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
+use strum::{Display, EnumString};
make_proj_elim!(
body,
@@ -31,7 +32,9 @@ impl From
for super::Body {
make_case_elim!(
species,
- #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+ #[derive(
+ Copy, Clone, Debug, Display, EnumString, PartialEq, Eq, Hash, Serialize, Deserialize,
+ )]
#[repr(u32)]
pub enum Species {
Duck = 0,
@@ -98,7 +101,9 @@ impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies {
make_case_elim!(
body_type,
- #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+ #[derive(
+ Copy, Clone, Debug, Display, EnumString, PartialEq, Eq, Hash, Serialize, Deserialize,
+ )]
#[repr(u32)]
pub enum BodyType {
Female = 0,
diff --git a/common/src/comp/pet.rs b/common/src/comp/pet.rs
index 78187c59cb..8f56628911 100644
--- a/common/src/comp/pet.rs
+++ b/common/src/comp/pet.rs
@@ -1,4 +1,4 @@
-use crate::comp::body::Body;
+use crate::comp::{body::Body, phys::Mass, quadruped_low, quadruped_medium, quadruped_small};
use crossbeam_utils::atomic::AtomicCell;
use serde::{Deserialize, Serialize};
use specs::Component;
@@ -40,10 +40,62 @@ pub fn is_tameable(body: &Body) -> bool {
// Currently only Quadruped animals can be tamed pending further work
// on the pets feature (allowing larger animals to be tamed will
// require balance issues to be addressed).
- matches!(
- body,
- Body::QuadrupedLow(_) | Body::QuadrupedMedium(_) | Body::QuadrupedSmall(_)
- )
+ match body {
+ Body::QuadrupedMedium(quad_med) =>
+ // NOTE: the reason we ban mammoth from being tameable even though they're
+ // agressive anyway, is that UncomfySilence is going to make them
+ // peaceful after this MR gets merged. Please, remove this note in your MR,
+ // UncomfySilence!
+ {
+ !matches!(
+ quad_med.species,
+ quadruped_medium::Species::Catoblepas
+ | quadruped_medium::Species::Mammoth
+ | quadruped_medium::Species::Hirdrasil
+ )
+ },
+ Body::QuadrupedLow(_) | Body::QuadrupedSmall(_) | Body::BirdMedium(_) => true,
+ _ => false,
+ }
+}
+
+pub fn is_mountable(mount: &Body, rider: Option<&Body>) -> bool {
+ let is_light_enough =
+ |rider: Option<&Body>| -> bool { rider.map_or(false, |b| b.mass() <= Mass(500.0)) };
+
+ match mount {
+ Body::QuadrupedMedium(body) => match body.species {
+ quadruped_medium::Species::Alpaca
+ | quadruped_medium::Species::Antelope
+ | quadruped_medium::Species::Bear
+ | quadruped_medium::Species::Camel
+ | quadruped_medium::Species::Cattle
+ | quadruped_medium::Species::Deer
+ | quadruped_medium::Species::Donkey
+ | quadruped_medium::Species::Highland
+ | quadruped_medium::Species::Horse
+ | quadruped_medium::Species::Kelpie
+ | quadruped_medium::Species::Llama
+ | quadruped_medium::Species::Moose
+ | quadruped_medium::Species::Tuskram
+ | quadruped_medium::Species::Yak
+ | quadruped_medium::Species::Zebra => true,
+ quadruped_medium::Species::Mouflon => is_light_enough(rider),
+ _ => false,
+ },
+ Body::QuadrupedSmall(body) => match body.species {
+ quadruped_small::Species::Truffler => true,
+ quadruped_small::Species::Boar | quadruped_small::Species::Holladon => {
+ is_light_enough(rider)
+ },
+ _ => false,
+ },
+ Body::QuadrupedLow(body) => matches!(
+ body.species,
+ quadruped_low::Species::Salamander | quadruped_low::Species::Tortoise
+ ),
+ _ => false,
+ }
}
impl Component for Pet {
diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs
index e352326a76..423fbaf6a9 100644
--- a/common/src/comp/phys.rs
+++ b/common/src/comp/phys.rs
@@ -81,7 +81,7 @@ impl Component for Scale {
}
// Mass
-#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Mass(pub f32);
impl Default for Mass {
diff --git a/common/src/mounting.rs b/common/src/mounting.rs
index bfc0c6b185..96b586cf69 100644
--- a/common/src/mounting.rs
+++ b/common/src/mounting.rs
@@ -1,5 +1,6 @@
use crate::{
comp,
+ comp::{pet::is_mountable, Body},
link::{Is, Link, LinkHandle, Role},
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
@@ -38,6 +39,7 @@ impl Link for Mounting {
Read<'a, UidAllocator>,
WriteStorage<'a, Is>,
WriteStorage<'a, Is>,
+ WriteStorage<'a, Body>,
);
type DeleteData<'a> = (
Read<'a, UidAllocator>,
@@ -58,7 +60,7 @@ impl Link for Mounting {
fn create(
this: &LinkHandle,
- (uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>,
+ (uid_allocator, mut is_mounts, mut is_riders, body): Self::CreateData<'_>,
) -> Result<(), Self::Error> {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
@@ -66,15 +68,23 @@ impl Link for Mounting {
// Forbid self-mounting
Err(MountingError::NotMountable)
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
- let can_mount_with =
- |entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none();
+ if let Some(mount_body) = body.get(mount) {
+ if is_mountable(mount_body, body.get(rider)) {
+ let can_mount_with =
+ |entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none();
- // Ensure that neither mount or rider are already part of a mounting
- // relationship
- if can_mount_with(mount) && can_mount_with(rider) {
- let _ = is_mounts.insert(mount, this.make_role());
- let _ = is_riders.insert(rider, this.make_role());
- Ok(())
+ // Ensure that neither mount or rider are already part of a mounting
+ // relationship
+ if can_mount_with(mount) && can_mount_with(rider) {
+ let _ = is_mounts.insert(mount, this.make_role());
+ let _ = is_riders.insert(rider, this.make_role());
+ Ok(())
+ } else {
+ Err(MountingError::NotMountable)
+ }
+ } else {
+ Err(MountingError::NotMountable)
+ }
} else {
Err(MountingError::NotMountable)
}
diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs
index 9637a9ea75..21cdd22b82 100644
--- a/server/src/persistence/character/conversions.rs
+++ b/server/src/persistence/character/conversions.rs
@@ -209,6 +209,10 @@ pub fn convert_body_to_database_json(
"quadruped_small",
serde_json::to_string(&GenericBody::from(body))?,
),
+ common::comp::Body::BirdMedium(body) => (
+ "bird_medium",
+ serde_json::to_string(&GenericBody::from(body))?,
+ ),
_ => {
return Err(PersistenceError::ConversionError(format!(
"Unsupported body type for persistence: {:?}",
@@ -577,6 +581,9 @@ pub fn convert_body_from_database(
"quadruped_small" => {
deserialize_body!(body_data, QuadrupedSmall, quadruped_small)
},
+ "bird_medium" => {
+ deserialize_body!(body_data, BirdMedium, bird_medium)
+ },
_ => {
return Err(PersistenceError::ConversionError(format!(
"{} is not a supported body type for deserialization",
diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs
index 610ecccc7a..30003655f3 100644
--- a/server/src/persistence/json_models.rs
+++ b/server/src/persistence/json_models.rs
@@ -59,6 +59,7 @@ macro_rules! generic_body_from_impl {
generic_body_from_impl!(comp::quadruped_low::Body);
generic_body_from_impl!(comp::quadruped_medium::Body);
generic_body_from_impl!(comp::quadruped_small::Body);
+generic_body_from_impl!(comp::bird_medium::Body);
#[derive(Serialize, Deserialize)]
pub struct CharacterPosition {
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index c02dd10a1d..3803fb0ec6 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -82,6 +82,7 @@ use common::{
fluid_dynamics,
inventory::{slot::InvSlotId, trade_pricing::TradePricing},
item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality},
+ pet::is_mountable,
skillset::{skills::Skill, SkillGroupKind},
BuffData, BuffKind, Item, MapMarkerChange,
},
@@ -1929,7 +1930,8 @@ impl Hud {
_,
health,
_,
- height_offset,
+ scale,
+ body,
hpfl,
in_group,
dist_sqr,
@@ -2034,23 +2036,10 @@ impl Hud {
} else {
None
};
-
(info.is_some() || bubble.is_some()).then(|| {
(
- entity,
- pos,
- info,
- bubble,
- stats,
- skill_set,
- health,
- buffs,
- body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
- hpfl,
- in_group,
- dist_sqr,
- alignment,
- is_mount,
+ entity, pos, info, bubble, stats, skill_set, health, buffs, scale,
+ body, hpfl, in_group, dist_sqr, alignment, is_mount,
)
})
},
@@ -2060,10 +2049,9 @@ impl Hud {
&mut self.ids.overheads,
&mut ui_widgets.widget_id_generator(),
);
- let ingame_pos = pos + Vec3::unit_z() * height_offset;
- //
- // * height_offset
+ let height_offset = body.height() * scale.map_or(1.0, |s| s.0) + 0.5;
+ let ingame_pos = pos + Vec3::unit_z() * height_offset;
// Speech bubble, name, level, and hp bars
overhead::Overhead::new(
@@ -2093,6 +2081,7 @@ impl Hud {
if Some(*owner) == client.uid()
&& !client.is_riding()
&& is_mount.is_none()
+ && is_mountable(body, bodies.get(client.entity()))
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
{
vec![(GameInput::Mount, i18n.get("hud.mount").to_string())]