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())]