1214 lines
34 KiB
Rust
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()
|
|
}
|
|
}
|