Files
MetaCrate/crates/libremetaverse/src/agent_messages.rs
Chili Palmer 9c9d5b91f1
All checks were successful
Native code generation / deterministic (push) Successful in 12m0s
Imaging and meshing gate / native (push) Successful in 4m3s
Native Rust workspace compile / compile (push) Successful in 4m4s
Implement native AgentManager services (#59)
2026-08-09 22:07:51 +00:00

1214 lines
34 KiB
Rust

//! 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<OSD, Error> {
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<crate::AvatarAvatarProperties, Error> {
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<OSD, Error> {
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<crate::AvatarInterests, Error> {
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<i32, Error> {
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<Self, Error> {
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<Self, Error> {
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<UUID, i32>,
}
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<Self, Error> {
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<UUID, i32>) -> Result<Self, Error> {
Ok(Self { animations })
}
#[must_use]
pub fn animations(&self) -> HashMap<UUID, i32> {
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<Object>) -> 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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<String, Error> {
map.get(key)
.map_or(Ok(String::new()), |value| value.as_string())
}
fn uuid(map: &OSDMap, key: &str) -> Result<UUID, Error> {
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<Vec<UUID>, 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<UUID>,
}
impl ExperienceListMessage {
pub fn new() -> Result<Self, Error> {
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, Error> {
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<OSDMap, Error> {
Self::serialize(self)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ExperiencePreferencesMessage {
pub allowed: Vec<UUID>,
pub blocked: Vec<UUID>,
}
impl ExperiencePreferencesMessage {
pub fn new() -> Result<Self, Error> {
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, Error> {
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<OSDMap, Error> {
Self::serialize(self)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RegionExperiencesMessage {
pub allowed: Vec<UUID>,
pub blocked: Vec<UUID>,
pub default: UUID,
pub trusted: Vec<UUID>,
}
impl RegionExperiencesMessage {
pub fn new() -> Result<Self, Error> {
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, Error> {
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<OSDMap, Error> {
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<Self, Error> {
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, Error> {
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<OSDMap, Error> {
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<Self, Error> {
Ok(Self::default())
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AvatarRenderInfoMessage {
pub agents: HashMap<UUID, AvatarRenderInfoMessageAvatarInfo>,
pub over_limit: i32,
pub reporting_limit: i32,
}
impl AvatarRenderInfoMessage {
pub fn new() -> Result<Self, Error> {
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<OSDMap, Error> {
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<OSDMap, Error> {
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<i32>,
pub picks_limit: i32,
pub sound_upload_cost: i32,
pub texture_upload_cost: i32,
}
impl ViewerBenefitsMessageBenefitPackage {
pub fn new() -> Result<Self, Error> {
Ok(Self::default())
}
fn deserialize(map: &OSDMap) -> Result<Self, Error> {
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::<Result<_, _>>()?,
_ => 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<String, ViewerBenefitsMessageBenefitPackage>,
}
impl ViewerBenefitsMessage {
pub fn new() -> Result<Self, Error> {
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<OSDMap, Error> {
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<OSDMap, Error> {
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<Self, Error> {
Ok(Self::default())
}
pub fn from_osd(map: OSDMap) -> Result<Self, Error> {
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, Error> {
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<ExperienceInfo>,
}
impl ExperienceInfoMessage {
pub fn new() -> Result<Self, Error> {
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<OSDMap, Error> {
let values = self
.experiences
.iter()
.map(|value| value.serialize().map(|map| OSD::Map(map.snapshot())))
.collect::<Result<Vec<_>, _>>()?;
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<OSDMap, Error> {
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<Self, Error> {
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<Self, Error> {
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()
}
}