//! Native LLSD messages owned by the non-movement agent service. #![allow(clippy::inherent_to_string)] // Public names mirror C# ToString mappings. #![allow(clippy::cast_possible_truncation)] // C# double-to-float mappings truncate identically. #![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API. #![allow(clippy::needless_pass_by_value)] // Owned parameters mirror mapped C# signatures. #![allow(clippy::struct_excessive_bools)] // Chat member flags are independent protocol fields. use crate::{Error, interfaces::IMessage, messages::linden::ExperienceFlags}; use libremetaverse_structured_data::{OSD, OSDMap}; use libremetaverse_types::UUID; use libremetaverse_types::Vector3; use libremetaverse_types::compat::Object; use std::collections::HashMap; pub(crate) fn avatar_properties_get_osd( value: &crate::AvatarAvatarProperties, ) -> Result { Ok(OSD::Map(HashMap::from([ ( "first_life_text".into(), OSD::String(value.first_life_text.clone()), ), ("first_life_image".into(), OSD::UUID(value.first_life_image)), ("partner".into(), OSD::UUID(value.partner)), ("about_text".into(), OSD::String(value.about_text.clone())), ("born_on".into(), OSD::String(value.born_on.clone())), ( "charter_member".into(), OSD::String(value.charter_member.clone()), ), ("profile_image".into(), OSD::UUID(value.profile_image)), ( "flags".into(), OSD::Integer(i32::try_from(value.flags.0).map_err(|_| Error::Argument)?), ), ("profile_url".into(), OSD::String(value.profile_url.clone())), ]))) } pub(crate) fn avatar_properties_from_osd( value: OSD, ) -> Result { let OSD::Map(map) = value else { return Err(Error::Argument); }; let string = |key: &str| map.get(key).map_or(Ok(String::new()), OSD::as_string); let uuid = |key: &str| map.get(key).map_or(Ok(UUID::zero()), OSD::as_uuid); Ok(crate::AvatarAvatarProperties { first_life_text: string("first_life_text")?, first_life_image: uuid("first_life_image")?, partner: uuid("partner")?, about_text: string("about_text")?, born_on: string("born_on")?, // Preserve the reference parser's historical `chart_member` spelling, // while accepting the serializer's corrected key for interoperability. charter_member: if map.contains_key("chart_member") { string("chart_member")? } else { string("charter_member")? }, profile_image: uuid("profile_image")?, flags: crate::ProfileFlags(map.get("flags").map_or(Ok(0), OSD::as_u_integer)?), profile_url: string("profile_url")?, }) } pub(crate) fn avatar_interests_get_osd(value: &crate::AvatarInterests) -> Result { Ok(OSD::Map(HashMap::from([ ( "languages_text".into(), OSD::String(value.languages_text.clone()), ), ( "skills_mask".into(), OSD::Integer(i32::try_from(value.skills_mask).map_err(|_| Error::Argument)?), ), ("skills_text".into(), OSD::String(value.skills_text.clone())), ( "want_to_mask".into(), OSD::Integer(i32::try_from(value.want_to_mask).map_err(|_| Error::Argument)?), ), ( "want_to_text".into(), OSD::String(value.want_to_text.clone()), ), ]))) } pub(crate) fn avatar_interests_from_osd(value: OSD) -> Result { let OSD::Map(map) = value else { return Err(Error::Argument); }; let string = |key: &str| map.get(key).map_or(Ok(String::new()), OSD::as_string); let integer = |key: &str| map.get(key).map_or(Ok(0), OSD::as_u_integer); Ok(crate::AvatarInterests { languages_text: string("languages_text")?, skills_mask: integer("skills_mask")?, skills_text: string("skills_text")?, want_to_mask: integer("want_to_mask")?, want_to_text: string("want_to_text")?, }) } fn integer(map: &OSDMap, key: &str) -> Result { map.get(key).map_or(Ok(0), |value| value.as_integer()) } #[derive(Clone)] pub struct ChatEventArgs { simulator: crate::Simulator, message: String, audible_level: crate::ChatAudibleLevel, type_: crate::ChatType, source_type: crate::ChatSourceType, from_name: String, source_id: UUID, owner_id: UUID, position: Vector3, } impl ChatEventArgs { #[allow(clippy::too_many_arguments)] pub fn new( simulator: crate::Simulator, message: String, audible: crate::ChatAudibleLevel, type_: crate::ChatType, source_type: crate::ChatSourceType, from_name: String, source_id: UUID, owner_id: UUID, position: Vector3, ) -> Result { Ok(Self { simulator, message, audible_level: audible, type_, source_type, from_name, source_id, owner_id, position, }) } #[must_use] pub fn simulator(&self) -> crate::Simulator { self.simulator.clone() } #[must_use] pub fn message(&self) -> String { self.message.clone() } #[must_use] pub const fn audible_level(&self) -> crate::ChatAudibleLevel { self.audible_level } #[must_use] pub const fn type_(&self) -> crate::ChatType { self.type_ } #[must_use] pub const fn source_type(&self) -> crate::ChatSourceType { self.source_type } #[must_use] pub fn from_name(&self) -> String { self.from_name.clone() } #[must_use] pub const fn source_id(&self) -> UUID { self.source_id } #[must_use] pub const fn owner_id(&self) -> UUID { self.owner_id } #[must_use] pub const fn position(&self) -> Vector3 { self.position } #[must_use] pub fn to_string(&self) -> String { format!( "[ChatEvent: Sim={}, Message={}, AudibleLevel={:?}, Type={:?}, SourceType={:?}, FromName={}, SourceID={}, Position={}, OwnerID={}]", self.simulator.native_to_string(), self.message, self.audible_level, self.type_, self.source_type, self.from_name, self.source_id, self.position.to_string(), self.owner_id ) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct AgentDataReplyEventArgs { first_name: String, last_name: String, active_group_id: UUID, group_title: String, group_powers: crate::GroupPowers, group_name: String, } impl AgentDataReplyEventArgs { pub const fn new( first_name: String, last_name: String, active_group_id: UUID, group_title: String, group_powers: crate::GroupPowers, group_name: String, ) -> Result { Ok(Self { first_name, last_name, active_group_id, group_title, group_powers, group_name, }) } #[must_use] pub fn first_name(&self) -> String { self.first_name.clone() } #[must_use] pub fn last_name(&self) -> String { self.last_name.clone() } #[must_use] pub const fn active_group_id(&self) -> UUID { self.active_group_id } #[must_use] pub fn group_title(&self) -> String { self.group_title.clone() } #[must_use] pub const fn group_powers(&self) -> crate::GroupPowers { self.group_powers } #[must_use] pub fn group_name(&self) -> String { self.group_name.clone() } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct AnimationsChangedEventArgs { animations: HashMap, } impl Clone for crate::messages::linden::NavMeshStatusUpdateMessage { fn clone(&self) -> Self { Self { raw_data: self.raw_data.clone(), region_id: self.region_id, status: self.status.clone(), version: self.version, } } } #[derive(Clone)] pub struct NavMeshStatusUpdateEventArgs { message: crate::messages::linden::NavMeshStatusUpdateMessage, simulator: crate::Simulator, } impl NavMeshStatusUpdateEventArgs { pub const fn new( message: crate::messages::linden::NavMeshStatusUpdateMessage, simulator: crate::Simulator, ) -> Result { Ok(Self { message, simulator }) } #[must_use] pub fn message(&self) -> crate::messages::linden::NavMeshStatusUpdateMessage { self.message.clone() } #[must_use] pub fn simulator(&self) -> crate::Simulator { self.simulator.clone() } } impl AnimationsChangedEventArgs { pub fn new(animations: HashMap) -> Result { Ok(Self { animations }) } #[must_use] pub fn animations(&self) -> HashMap { self.animations.clone() } } /// A participant and their current moderation state in a group-chat session. #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub struct ChatSessionMember { pub avatar_key: UUID, pub can_voice_chat: bool, pub is_moderator: bool, pub mute_text: bool, pub mute_voice: bool, } impl ChatSessionMember { #[must_use] pub fn equals_with_chat_session_member(&self, other: Self) -> bool { self.avatar_key == other.avatar_key && self.can_voice_chat == other.can_voice_chat && self.is_moderator == other.is_moderator && self.mute_text == other.mute_text && self.mute_voice == other.mute_voice } /// `System.Object` cannot carry arbitrary boxed compatibility structs, so /// this overload can only reject the supported primitive object variants. #[must_use] pub fn equals_with_object(&self, _obj: Option) -> bool { false } /// Matches the unchecked C# hash composition used by `ChatSessionMember`. #[must_use] pub fn get_hash_code(&self) -> i32 { let mut hash = self.avatar_key.get_hash_code(); for value in [ self.can_voice_chat, self.is_moderator, self.mute_text, self.mute_voice, ] { hash = hash.wrapping_mul(397) ^ i32::from(value); } hash } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ChatSessionMemberAddedEventArgs { session_id: UUID, agent_id: UUID, } impl ChatSessionMemberAddedEventArgs { pub const fn new(session_id: UUID, agent_id: UUID) -> Result { Ok(Self { session_id, agent_id, }) } #[must_use] pub const fn session_id(&self) -> UUID { self.session_id } #[must_use] pub const fn agent_id(&self) -> UUID { self.agent_id } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ChatSessionMemberLeftEventArgs { session_id: UUID, agent_id: UUID, } impl ChatSessionMemberLeftEventArgs { pub const fn new(session_id: UUID, agent_id: UUID) -> Result { Ok(Self { session_id, agent_id, }) } #[must_use] pub const fn session_id(&self) -> UUID { self.session_id } #[must_use] pub const fn agent_id(&self) -> UUID { self.agent_id } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupChatJoinedEventArgs { session_id: UUID, session_name: String, tmp_session_id: UUID, success: bool, } impl GroupChatJoinedEventArgs { pub const fn new( group_chat_session_id: UUID, session_name: String, tmp_session_id: UUID, success: bool, ) -> Result { Ok(Self { session_id: group_chat_session_id, session_name, tmp_session_id, success, }) } #[must_use] pub const fn session_id(&self) -> UUID { self.session_id } #[must_use] pub fn session_name(&self) -> String { self.session_name.clone() } #[must_use] pub const fn tmp_session_id(&self) -> UUID { self.tmp_session_id } #[must_use] pub const fn success(&self) -> bool { self.success } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct SetDisplayNameReplyEventArgs { status: i32, reason: String, display_name: crate::AgentDisplayName, } impl SetDisplayNameReplyEventArgs { pub const fn new( status: i32, reason: String, display_name: crate::AgentDisplayName, ) -> Result { Ok(Self { status, reason, display_name, }) } #[must_use] pub const fn status(&self) -> i32 { self.status } #[must_use] pub fn reason(&self) -> String { self.reason.clone() } #[must_use] pub fn display_name(&self) -> crate::AgentDisplayName { self.display_name.clone() } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct BalanceEventArgs { balance: i32, } impl BalanceEventArgs { pub const fn new(balance: i32) -> Result { Ok(Self { balance }) } #[must_use] pub const fn balance(&self) -> i32 { self.balance } } impl Clone for crate::TransactionInfo { fn clone(&self) -> Self { Self { amount: self.amount, dest_id: self.dest_id, is_dest_group: self.is_dest_group, is_source_group: self.is_source_group, item_description: self.item_description.clone(), source_id: self.source_id, transaction_type: self.transaction_type, } } } #[derive(Clone)] pub struct MoneyBalanceReplyEventArgs { transaction_id: UUID, success: bool, balance: i32, meters_credit: i32, meters_committed: i32, description: String, transaction_info: crate::TransactionInfo, } impl MoneyBalanceReplyEventArgs { #[allow(clippy::too_many_arguments)] pub fn new( transaction_id: UUID, transaction_success: bool, balance: i32, meters_credit: i32, meters_committed: i32, description: String, transaction_info: crate::TransactionInfo, ) -> Result { Ok(Self { transaction_id, success: transaction_success, balance, meters_credit, meters_committed, description, transaction_info, }) } #[must_use] pub const fn transaction_id(&self) -> UUID { self.transaction_id } #[must_use] pub const fn success(&self) -> bool { self.success } #[must_use] pub const fn balance(&self) -> i32 { self.balance } #[must_use] pub const fn meters_credit(&self) -> i32 { self.meters_credit } #[must_use] pub const fn meters_committed(&self) -> i32 { self.meters_committed } #[must_use] pub fn description(&self) -> String { self.description.clone() } #[must_use] pub fn transaction_info(&self) -> crate::TransactionInfo { self.transaction_info.clone() } } fn string(map: &OSDMap, key: &str) -> Result { map.get(key) .map_or(Ok(String::new()), |value| value.as_string()) } fn uuid(map: &OSDMap, key: &str) -> Result { map.get(key) .map_or(Ok(UUID::zero()), |value| value.as_uuid()) } fn empty_map() -> OSDMap { match OSDMap::new_with_dictionary(HashMap::new()) { Ok(map) => map, Err(_) => unreachable!("an empty OSD map is always valid"), } } fn decode_ids(map: &OSDMap, key: &str) -> Result, Error> { let Some(OSD::Array(values)) = map.get(key) else { return Ok(Vec::new()); }; values.iter().map(OSD::as_uuid).collect() } fn encode_ids(values: &[UUID]) -> OSD { OSD::Array(values.iter().copied().map(OSD::UUID).collect()) } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ExperienceListMessage { pub experience_i_ds: Vec, } impl ExperienceListMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.experience_i_ds = decode_ids(&map, "experience_ids")?; Ok(()) } pub fn serialize(&self) -> Result { OSDMap::new_with_dictionary(HashMap::from([( "experience_ids".into(), encode_ids(&self.experience_i_ds), )])) } } impl IMessage for ExperienceListMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ExperiencePreferencesMessage { pub allowed: Vec, pub blocked: Vec, } impl ExperiencePreferencesMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.allowed = decode_ids(&map, "experiences")?; self.blocked = decode_ids(&map, "blocked")?; Ok(()) } pub fn serialize(&self) -> Result { OSDMap::new_with_dictionary(HashMap::from([ ("experiences".into(), encode_ids(&self.allowed)), ("blocked".into(), encode_ids(&self.blocked)), ])) } } impl IMessage for ExperiencePreferencesMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RegionExperiencesMessage { pub allowed: Vec, pub blocked: Vec, pub default: UUID, pub trusted: Vec, } impl RegionExperiencesMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.blocked = decode_ids(&map, "blocked")?; self.trusted = decode_ids(&map, "trusted")?; self.allowed = decode_ids(&map, "allowed")?; self.default = map .get("default") .map_or(Ok(UUID::zero()), |value| value.as_uuid())?; Ok(()) } pub fn serialize(&self) -> Result { OSDMap::new_with_dictionary(HashMap::from([ ("blocked".into(), encode_ids(&self.blocked)), ("trusted".into(), encode_ids(&self.trusted)), ("allowed".into(), encode_ids(&self.allowed)), ("default".into(), OSD::UUID(self.default)), ])) } } impl IMessage for RegionExperiencesMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } #[derive(Clone)] pub struct AgentPreferencesMessage { pub hover_height: f32, pub raw_data: OSDMap, } impl Default for AgentPreferencesMessage { fn default() -> Self { Self { hover_height: 0.0, raw_data: empty_map(), } } } impl AgentPreferencesMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.hover_height = map .get("hover_height") .map_or(Ok(0.0), |value| value.as_real())? as f32; self.raw_data = map; Ok(()) } pub fn serialize(&self) -> Result { OSDMap::new_with_dictionary(HashMap::from([( "hover_height".into(), OSD::Real(f64::from(self.hover_height)), )])) } } impl IMessage for AgentPreferencesMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AvatarRenderInfoMessageAvatarInfo { pub too_complex: bool, pub weight: i32, } impl AvatarRenderInfoMessageAvatarInfo { pub fn new() -> Result { Ok(Self::default()) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AvatarRenderInfoMessage { pub agents: HashMap, pub over_limit: i32, pub reporting_limit: i32, } impl AvatarRenderInfoMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.agents.clear(); if let Some(OSD::Map(agents)) = map.get("agents") { for (id, value) in agents { let Ok(id) = UUID::new_with_string(id.clone()) else { continue; }; let OSD::Map(info) = value else { continue; }; self.agents.insert( id, AvatarRenderInfoMessageAvatarInfo { weight: info.get("weight").map_or(Ok(0), OSD::as_integer)?, too_complex: info.get("tooComplex").map_or(Ok(false), OSD::as_boolean)?, }, ); } } self.over_limit = integer(&map, "overlimit")?; self.reporting_limit = integer(&map, "reportinglimit")?; Ok(()) } pub fn serialize(&self) -> Result { let agents = self .agents .iter() .map(|(id, info)| { ( id.to_string(), OSD::Map(HashMap::from([ ("weight".into(), OSD::Integer(info.weight)), ("tooComplex".into(), OSD::Boolean(info.too_complex)), ])), ) }) .collect(); OSDMap::new_with_dictionary(HashMap::from([("agents".into(), OSD::Map(agents))])) } } impl IMessage for AvatarRenderInfoMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ViewerBenefitsMessageBenefitPackage { pub animated_object_limit: i32, pub animation_upload_cost: i32, pub attachment_limit: i32, pub create_group_cost: i32, pub group_membership_limit: i32, pub large_texture_upload_cost: Vec, pub picks_limit: i32, pub sound_upload_cost: i32, pub texture_upload_cost: i32, } impl ViewerBenefitsMessageBenefitPackage { pub fn new() -> Result { Ok(Self::default()) } fn deserialize(map: &OSDMap) -> Result { Ok(Self { animated_object_limit: integer(map, "animated_object_limit")?, animation_upload_cost: integer(map, "animation_upload_cost")?, attachment_limit: integer(map, "attachment_limit")?, create_group_cost: integer(map, "create_group_cost")?, group_membership_limit: integer(map, "group_membership_limit")?, large_texture_upload_cost: match map.get("large_texture_upload_cost") { Some(OSD::Array(values)) => values .iter() .map(OSD::as_integer) .collect::>()?, _ => Vec::new(), }, picks_limit: integer(map, "picks_limit")?, sound_upload_cost: integer(map, "sound_upload_cost")?, texture_upload_cost: integer(map, "texture_upload_cost")?, }) } fn serialize(&self) -> OSD { let mut values = HashMap::from([ ( "animated_object_limit".into(), OSD::Integer(self.animated_object_limit), ), ( "animation_upload_cost".into(), OSD::Integer(self.animation_upload_cost), ), ( "attachment_limit".into(), OSD::Integer(self.attachment_limit), ), ( "create_group_cost".into(), OSD::Integer(self.create_group_cost), ), ( "group_membership_limit".into(), OSD::Integer(self.group_membership_limit), ), ("picks_limit".into(), OSD::Integer(self.picks_limit)), ( "sound_upload_cost".into(), OSD::Integer(self.sound_upload_cost), ), ( "texture_upload_cost".into(), OSD::Integer(self.texture_upload_cost), ), ]); if !self.large_texture_upload_cost.is_empty() { values.insert( "large_texture_upload_cost".into(), OSD::Array( self.large_texture_upload_cost .iter() .copied() .map(OSD::Integer) .collect(), ), ); } OSD::Map(values) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ViewerBenefitsMessage { pub account_level_benefits: ViewerBenefitsMessageBenefitPackage, pub account_type: String, pub premium_packages: HashMap, } impl ViewerBenefitsMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.account_type = string(&map, "account_type")?; self.account_level_benefits = match map.get("account_level_benefits") { Some(OSD::Map(values)) => ViewerBenefitsMessageBenefitPackage::deserialize( &OSDMap::new_with_dictionary(values.clone())?, )?, _ => ViewerBenefitsMessageBenefitPackage::default(), }; self.premium_packages.clear(); if let Some(OSD::Map(packages)) = map.get("premium_packages") { for (name, value) in packages { let OSD::Map(entry) = value else { continue }; let Some(OSD::Map(benefits)) = entry.get("benefits") else { continue; }; let benefits = OSDMap::new_with_dictionary(benefits.clone())?; self.premium_packages.insert( name.clone(), ViewerBenefitsMessageBenefitPackage::deserialize(&benefits)?, ); } } Ok(()) } pub fn serialize(&self) -> Result { let packages = self .premium_packages .iter() .map(|(name, benefits)| { ( name.clone(), OSD::Map(HashMap::from([("benefits".into(), benefits.serialize())])), ) }) .collect(); OSDMap::new_with_dictionary(HashMap::from([ ( "account_type".into(), OSD::String(self.account_type.clone()), ), ( "account_level_benefits".into(), self.account_level_benefits.serialize(), ), ("premium_packages".into(), OSD::Map(packages)), ])) } } impl IMessage for ViewerBenefitsMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExperienceInfo { pub agent_id: UUID, pub description: String, pub experience_id: UUID, pub extended_metadata: String, pub flags: ExperienceFlags, pub group_id: UUID, pub image_id: UUID, pub marketplace: String, pub maturity: i32, pub name: String, pub public_id: UUID, pub quota: i32, pub slurl: String, } impl Default for ExperienceInfo { fn default() -> Self { Self { agent_id: UUID::zero(), description: String::new(), experience_id: UUID::zero(), extended_metadata: String::new(), flags: ExperienceFlags::NONE, group_id: UUID::zero(), image_id: UUID::zero(), marketplace: String::new(), maturity: 0, name: String::new(), public_id: UUID::zero(), quota: 0, slurl: String::new(), } } } impl ExperienceInfo { pub fn new() -> Result { Ok(Self::default()) } pub fn from_osd(map: OSDMap) -> Result { Ok(Self { agent_id: uuid(&map, "agent_id")?, description: string(&map, "description")?, experience_id: uuid(&map, "experience_id")?, extended_metadata: string(&map, "extended_metadata")?, flags: ExperienceFlags(integer(&map, "properties")?), group_id: uuid(&map, "group_id")?, image_id: uuid(&map, "image_id")?, marketplace: string(&map, "marketplace")?, maturity: integer(&map, "maturity")?, name: string(&map, "name")?, public_id: uuid(&map, "public_id")?, quota: integer(&map, "quota")?, slurl: string(&map, "slurl")?, }) } pub fn serialize(&self) -> Result { OSDMap::new_with_dictionary(HashMap::from([ ("experience_id".into(), OSD::UUID(self.experience_id)), ("public_id".into(), OSD::UUID(self.public_id)), ("name".into(), OSD::String(self.name.clone())), ("description".into(), OSD::String(self.description.clone())), ("properties".into(), OSD::Integer(self.flags.0)), ("group_id".into(), OSD::UUID(self.group_id)), ("agent_id".into(), OSD::UUID(self.agent_id)), ("quota".into(), OSD::Integer(self.quota)), ("maturity".into(), OSD::Integer(self.maturity)), ("image_id".into(), OSD::UUID(self.image_id)), ("marketplace".into(), OSD::String(self.marketplace.clone())), ( "extended_metadata".into(), OSD::String(self.extended_metadata.clone()), ), ("slurl".into(), OSD::String(self.slurl.clone())), ])) } } #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ExperienceInfoMessage { pub experiences: Vec, } impl ExperienceInfoMessage { pub fn new() -> Result { Ok(Self::default()) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.experiences.clear(); if let Some(OSD::Array(values)) = map.get("experience_keys") { for value in values { if let OSD::Map(value) = value { self.experiences .push(ExperienceInfo::from_osd(OSDMap::new_with_dictionary( value.clone(), )?)?); } } } Ok(()) } pub fn serialize(&self) -> Result { let values = self .experiences .iter() .map(|value| value.serialize().map(|map| OSD::Map(map.snapshot()))) .collect::, _>>()?; OSDMap::new_with_dictionary(HashMap::from([( "experience_keys".into(), OSD::Array(values), )])) } } impl IMessage for ExperienceInfoMessage { fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { Self::deserialize(self, map) } fn serialize(&self) -> Result { Self::serialize(self) } } macro_rules! message_event_args { ($name:ident, $field:ident, $message:ty, $getter:ident) => { #[derive(Clone)] pub struct $name { $field: $message, } impl $name { pub fn new($field: $message) -> Result { Ok(Self { $field }) } #[must_use] pub fn $getter(&self) -> $message { self.$field.clone() } } }; } message_event_args!( AgentExperiencesEventArgs, agent_experiences, ExperienceListMessage, agent_experiences ); message_event_args!( ExperiencePreferencesEventArgs, preferences, ExperiencePreferencesMessage, preferences ); message_event_args!( RegionExperiencesEventArgs, region_experiences, RegionExperiencesMessage, region_experiences ); message_event_args!( ExperienceInfoEventArgs, experience_info, ExperienceInfoMessage, experience_info ); message_event_args!( AgentPreferencesEventArgs, preferences, AgentPreferencesMessage, preferences ); message_event_args!( AvatarRenderInfoEventArgs, render_info, AvatarRenderInfoMessage, render_info ); message_event_args!( ViewerBenefitsEventArgs, benefits, ViewerBenefitsMessage, benefits ); message_event_args!( ProductInfoEventArgs, product_info, crate::agent_manager::ProductInfoRequestMessage, product_info ); #[derive(Clone, Debug, Eq, PartialEq)] pub struct AgentAccessEventArgs { success: bool, new_level: String, } impl AgentAccessEventArgs { pub fn new(success: bool, new_level: String) -> Result { Ok(Self { success, new_level }) } #[must_use] pub fn success(&self) -> bool { self.success } #[must_use] pub fn new_level(&self) -> String { self.new_level.clone() } }