diff --git a/.gitea/workflows/codegen.yml b/.gitea/workflows/codegen.yml index 6b64086..c898211 100644 --- a/.gitea/workflows/codegen.yml +++ b/.gitea/workflows/codegen.yml @@ -13,6 +13,7 @@ on: - "tools/check_api_coverage.py" - "tools/check_codegen_gate.py" - "tools/check_milestone_08.py" + - "tools/check_milestone_09_issue_59.py" - "crates/libremetaverse/Cargo.toml" - "crates/libremetaverse/src/generated.rs" - "crates/libremetaverse/src/packets.rs" @@ -37,6 +38,7 @@ on: - "tools/check_api_coverage.py" - "tools/check_codegen_gate.py" - "tools/check_milestone_08.py" + - "tools/check_milestone_09_issue_59.py" - "crates/libremetaverse/Cargo.toml" - "crates/libremetaverse/src/generated.rs" - "crates/libremetaverse/src/packets.rs" @@ -69,6 +71,7 @@ jobs: python3 tools/generate_api_shims.py --check python3 tools/check_api_coverage.py python3 tools/check_milestone_08.py + python3 tools/check_milestone_09_issue_59.py cargo check --manifest-path tests/api-compile/Cargo.toml --locked - name: Test generated wire codecs and translated parity cases run: | diff --git a/.gitea/workflows/rust-workspace.yml b/.gitea/workflows/rust-workspace.yml index d4df1d7..0207968 100644 --- a/.gitea/workflows/rust-workspace.yml +++ b/.gitea/workflows/rust-workspace.yml @@ -25,5 +25,7 @@ jobs: components: rustfmt - name: Check formatting run: cargo fmt --all -- --check + - name: Audit native AgentManager boundary + run: python3 tools/check_milestone_09_issue_59.py - name: Compile every workspace target with bounded memory run: cargo check --workspace --all-targets --locked -j 1 diff --git a/README.md b/README.md index aa65a66..f175984 100644 --- a/README.md +++ b/README.md @@ -324,3 +324,18 @@ counters and receives real UDP and server-stat updates without exposing packet payloads or endpoints. The gate asserts token redaction, bounded policies, and joined CAPS/download/network tasks; `tools/check_milestone_08.py` prevents owned networking stubs or required audit evidence from regressing. + +### Milestone 09 + +The first world-services slice provides a native non-movement `AgentManager` +over the milestone 08 transport. Chat, instant messages and conferences, +gestures, animation, viewer effects, balance and payments, profiles, notes, +picks, classifieds, display names, language/access/preferences, experiences, +viewer reporting, benefits, attachment resources, mute lists, and nav-mesh +status now use their C#-compatible LLUDP or capability paths. Incoming state is +updated before callbacks, and callbacks run outside synchronization locks. +Gesture assets have a native version-2 parser and executor with bounded HTTP +and UDP download paths. An offline fake grid verifies capability discovery and +the resulting chat, animation, and sound packets. The ownership, cancellation, +limits, and compatibility rules are documented in +[`docs/agent-manager.md`](docs/agent-manager.md). diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 5752065..1992d7a 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | Assembly | Types | Members | Status | |---|---:|---:|---| -| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 79 types / 14,127 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 121 types / 14,470 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain | | `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | diff --git a/crates/libremetaverse/src/agent_manager.rs b/crates/libremetaverse/src/agent_manager.rs new file mode 100644 index 0000000..872d715 --- /dev/null +++ b/crates/libremetaverse/src/agent_manager.rs @@ -0,0 +1,4704 @@ +//! Native non-movement agent services and event delivery. + +#![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::too_many_arguments)] // Packet-oriented signatures mirror the reference API. +#![allow(clippy::unnecessary_wraps)] // Generated callers require mapped Result signatures. +#![allow(clippy::unused_self)] // Instance shape is part of the mapped callback surface. +#![allow(clippy::if_not_else)] // Protocol mismatch handling is clearest as the early branch. + +use crate::messages::linden::AgentStateUpdateMessage; +use crate::messages::linden::{ + AgentPreferencesMessage, AvatarRenderInfoMessage, ExperienceInfo, ExperienceInfoMessage, + ExperienceListMessage, ExperiencePreferencesMessage, RegionExperiencesMessage, + ViewerBenefitsMessage, ViewerStatsMessage, +}; +use crate::{ + AgentAccessEventArgs, AgentDataReplyEventArgs, AgentExperiencesEventArgs, + AgentPreferencesEventArgs, AnimationsChangedEventArgs, AvatarRenderInfoEventArgs, + BalanceEventArgs, ChatEventArgs, ChatSessionMemberAddedEventArgs, + ChatSessionMemberLeftEventArgs, ExperienceInfoEventArgs, ExperiencePreferencesEventArgs, + GroupChatJoinedEventArgs, MoneyBalanceReplyEventArgs, NavMeshStatusUpdateEventArgs, + ProductInfoEventArgs, RegionExperiencesEventArgs, SetDisplayNameReplyEventArgs, + ViewerBenefitsEventArgs, +}; +use crate::{ + AgentFlags, AgentManagerAgentMovement, AgentManagerAgentMovementAgentCamera, AgentState, + ChatSessionMember, ChatType, EffectType, Error, GridClient, InstantMessageDialog, + InstantMessageOnline, LookAtType, MoneyTransactionType, MuteEntry, PointAtType, Simulator, + TransactionFlags, network_manager::CapsEventQueueCallback, +}; +use libremetaverse_structured_data::{OSD, OSDMap, OSDParser}; +use libremetaverse_types::compat::{EventHandler, Subscription, Uri}; +use libremetaverse_types::{Color4, Quaternion, UUID, Vector3, Vector3d}; +use std::collections::{BTreeMap, HashMap}; +use std::fmt; +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::{Duration, Instant, UNIX_EPOCH}; + +const MAX_CHAT_BYTES: usize = 1023; +const MAX_DIALOG_LABEL_BYTES: usize = 254; +const MAX_GESTURE_ASSET_BYTES: usize = 16 * 1024 * 1024; +const MAX_MUTE_LIST_BYTES: usize = 16 * 1024 * 1024; +const GESTURE_TIMEOUT: Duration = Duration::from_secs(30); + +struct MuteXfer { + announced_size: Option, + bytes: Vec, + next_packet: u32, + started: Instant, +} + +impl Default for MuteXfer { + fn default() -> Self { + Self { + announced_size: None, + bytes: Vec::new(), + next_packet: 0, + started: Instant::now(), + } + } +} + +fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { + value + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn send_simulator_packet( + simulator: &Simulator, + packet_type: crate::packets::PacketType, + data: Vec, +) -> Result<(), Error> { + let length = i32::try_from(data.len()).map_err(|_| Error::Argument)?; + let zerocoded = data + .first() + .is_some_and(|flags| flags & crate::Helpers::MSG_ZEROCODED != 0); + simulator.native_send_packet_data(data, length, packet_type, zerocoded) +} + +fn parse_mute_list(text: &str) -> HashMap { + let mut result = HashMap::new(); + for line in text.lines().map(str::trim).filter(|line| !line.is_empty()) { + let mut fields = line.splitn(3, char::is_whitespace); + let (Some(type_text), Some(id_text), Some(name_and_flags)) = + (fields.next(), fields.next(), fields.next()) + else { + continue; + }; + let Ok(type_number) = type_text.parse::() else { + continue; + }; + let type_ = match type_number { + 0 => crate::MuteType::ByName, + 1 => crate::MuteType::Resident, + 2 => crate::MuteType::Object, + 3 => crate::MuteType::Group, + 4 => crate::MuteType::External, + _ => continue, + }; + let Ok(id) = UUID::new_with_string(id_text.to_owned()) else { + continue; + }; + let (name, flags) = name_and_flags + .rsplit_once('|') + .map_or((name_and_flags, 0), |(name, flags)| { + (name, flags.trim().parse::().unwrap_or(0)) + }); + let name = name.trim().to_owned(); + if name.is_empty() { + continue; + } + result.insert( + format!("{id}|{name}"), + MuteEntry { + flags: crate::MuteFlags(flags), + id, + name, + type_, + }, + ); + } + result +} + +struct EventRegistryState { + next_id: AtomicU64, + handlers: Mutex)>>, +} + +struct EventRegistry { + state: Arc>, +} + +impl Default for EventRegistry { + fn default() -> Self { + Self { + state: Arc::new(EventRegistryState { + next_id: AtomicU64::new(1), + handlers: Mutex::new(Vec::new()), + }), + } + } +} + +impl EventRegistry { + fn subscribe(&self, handler: EventHandler) -> Subscription { + let id = self.state.next_id.fetch_add(1, Ordering::Relaxed); + mutex(&self.state.handlers).push((id, handler)); + let state = Arc::downgrade(&self.state); + Subscription::new(move || { + if let Some(state) = state.upgrade() { + mutex(&state.handlers).retain(|(candidate, _)| *candidate != id); + } + }) + } + + fn emit(&self, value: T) { + let handlers: Vec<_> = mutex(&self.state.handlers) + .iter() + .map(|(_, handler)| Arc::clone(handler)) + .collect(); + for handler in handlers { + let argument = value.clone(); + let _ = catch_unwind(AssertUnwindSafe(|| handler(argument))); + } + } +} + +#[derive(Clone)] +pub struct InstantMessage { + pub binary_bucket: Vec, + pub dialog: InstantMessageDialog, + pub from_agent_id: UUID, + pub from_agent_name: String, + pub group_im: bool, + pub im_session_id: UUID, + pub message: String, + pub offline: InstantMessageOnline, + pub parent_estate_id: u32, + pub position: Vector3, + pub region_id: UUID, + pub timestamp: std::time::SystemTime, + pub to_agent_id: UUID, +} + +impl fmt::Debug for InstantMessage { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("InstantMessage") + .field("dialog", &self.dialog) + .field("from_agent_id", &self.from_agent_id) + .field("group_im", &self.group_im) + .field("offline", &self.offline) + .field("parent_estate_id", &self.parent_estate_id) + .field("region_id", &self.region_id) + .field("timestamp", &self.timestamp) + .field("to_agent_id", &self.to_agent_id) + .field("message_len", &self.message.len()) + .field("binary_bucket_len", &self.binary_bucket.len()) + .finish_non_exhaustive() + } +} + +impl InstantMessage { + #[must_use] + pub fn to_string(&self) -> String { + format!("{}: {}", self.from_agent_name, self.message) + } +} + +#[derive(Clone)] +pub struct InstantMessageEventArgs { + im: InstantMessage, + simulator: Option, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ProductInfoRequestMessageProductInfo { + pub description: String, + pub name: String, + pub sku: String, +} + +impl ProductInfoRequestMessageProductInfo { + pub fn new() -> Result { + Ok(Self::default()) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ProductInfoRequestMessage { + pub products: Vec, +} + +impl ProductInfoRequestMessage { + pub fn new() -> Result { + Ok(Self::default()) + } + + pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { + self.products.clear(); + let Some(OSD::Array(products)) = map.get("products") else { + return Ok(()); + }; + for product in products { + let OSD::Map(product) = product else { + continue; + }; + self.products.push(ProductInfoRequestMessageProductInfo { + description: product + .get("description") + .map_or(Ok(String::new()), OSD::as_string)?, + name: product + .get("name") + .map_or(Ok(String::new()), OSD::as_string)?, + sku: product + .get("sku") + .map_or(Ok(String::new()), OSD::as_string)?, + }); + } + Ok(()) + } + + pub fn serialize(&self) -> Result { + OSDMap::new_with_dictionary(HashMap::from([( + "products".into(), + OSD::Array( + self.products + .iter() + .map(|product| { + OSD::Map(HashMap::from([ + ("sku".into(), OSD::String(product.sku.clone())), + ("name".into(), OSD::String(product.name.clone())), + ( + "description".into(), + OSD::String(product.description.clone()), + ), + ])) + }) + .collect(), + ), + )])) + } +} + +impl crate::interfaces::IMessage for ProductInfoRequestMessage { + fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { + Self::deserialize(self, map) + } + + fn serialize(&self) -> Result { + Self::serialize(self) + } +} + +impl InstantMessageEventArgs { + pub fn new(im: InstantMessage, simulator: Option) -> Result { + Ok(Self { im, simulator }) + } + + #[must_use] + pub fn im(&self) -> InstantMessage { + self.im.clone() + } + + #[must_use] + pub fn simulator(&self) -> Option { + self.simulator.clone() + } +} + +pub(crate) struct AgentManagerInner { + disposed: AtomicBool, + agent_id: RwLock, + session_id: RwLock, + first_name: RwLock, + last_name: RwLock, + active_group: RwLock, + active_group_powers: RwLock, + agent_access: RwLock, + hover_height: RwLock, + balance: RwLock, + active_gestures: RwLock>, + signaled_animations: Arc>>, + mute_list: Arc>>, + mute_xfers: Mutex>, + instant_message: EventRegistry, + agent_data_reply: EventRegistry, + animations_changed: EventRegistry, + agent_experiences: RwLock>, + experience_preferences: RwLock>, + region_experiences: RwLock>, + agent_preferences: RwLock>, + avatar_render_info: RwLock>, + viewer_benefits: RwLock>, + benefits: RwLock>, + last_nav_mesh_status: RwLock>, + product_info: RwLock>, + group_chat_sessions: Arc>>>, + network: Mutex>, + raw_packet_subscription: Mutex>, + caps_subscriptions: Mutex>, + chat_from_simulator: EventRegistry, + money_balance: EventRegistry, + money_balance_reply: EventRegistry, + agent_experiences_updated: EventRegistry, + experience_preferences_updated: EventRegistry, + region_experiences_updated: EventRegistry, + experience_info_received: EventRegistry, + agent_preferences_updated: EventRegistry, + avatar_render_info_updated: EventRegistry, + viewer_benefits_updated: EventRegistry, + product_info_updated: EventRegistry, + group_chat_joined: EventRegistry, + chat_session_member_added: EventRegistry, + chat_session_member_left: EventRegistry, + set_display_name_reply: EventRegistry, + nav_mesh_status_update: EventRegistry, + mute_list_updated: EventRegistry<()>, +} + +impl Default for AgentManagerInner { + fn default() -> Self { + Self { + disposed: AtomicBool::new(false), + agent_id: RwLock::new(UUID::zero()), + session_id: RwLock::new(UUID::zero()), + first_name: RwLock::new(String::new()), + last_name: RwLock::new(String::new()), + active_group: RwLock::new(UUID::zero()), + active_group_powers: RwLock::new(crate::GroupPowers(0)), + agent_access: RwLock::new(String::new()), + hover_height: RwLock::new(0.0), + balance: RwLock::new(0), + active_gestures: RwLock::new(HashMap::new()), + signaled_animations: Arc::new(RwLock::new(HashMap::new())), + mute_list: Arc::new(RwLock::new(HashMap::new())), + mute_xfers: Mutex::new(HashMap::new()), + instant_message: EventRegistry::default(), + agent_data_reply: EventRegistry::default(), + animations_changed: EventRegistry::default(), + agent_experiences: RwLock::new(None), + experience_preferences: RwLock::new(None), + region_experiences: RwLock::new(None), + agent_preferences: RwLock::new(None), + avatar_render_info: RwLock::new(None), + viewer_benefits: RwLock::new(None), + benefits: RwLock::new(None), + last_nav_mesh_status: RwLock::new(None), + product_info: RwLock::new(None), + group_chat_sessions: Arc::new(RwLock::new(HashMap::new())), + network: Mutex::new(None), + raw_packet_subscription: Mutex::new(None), + caps_subscriptions: Mutex::new(Vec::new()), + chat_from_simulator: EventRegistry::default(), + money_balance: EventRegistry::default(), + money_balance_reply: EventRegistry::default(), + agent_experiences_updated: EventRegistry::default(), + experience_preferences_updated: EventRegistry::default(), + region_experiences_updated: EventRegistry::default(), + experience_info_received: EventRegistry::default(), + agent_preferences_updated: EventRegistry::default(), + avatar_render_info_updated: EventRegistry::default(), + viewer_benefits_updated: EventRegistry::default(), + product_info_updated: EventRegistry::default(), + group_chat_joined: EventRegistry::default(), + chat_session_member_added: EventRegistry::default(), + chat_session_member_left: EventRegistry::default(), + set_display_name_reply: EventRegistry::default(), + nav_mesh_status_update: EventRegistry::default(), + mute_list_updated: EventRegistry::default(), + } + } +} + +impl AgentManagerInner { + fn handle_raw_packet(&self, event: crate::network_manager::RawPacketReceivedEventArgs) { + let result = match event.packet_type { + crate::packets::PacketType::ChatFromSimulator => { + self.handle_chat_packet(&event.data, event.simulator) + } + crate::packets::PacketType::ImprovedInstantMessage => { + self.handle_instant_message_packet(&event.data, event.simulator) + } + crate::packets::PacketType::MoneyBalanceReply => { + self.handle_money_balance_packet(&event.data) + } + crate::packets::PacketType::AgentDataUpdate => { + self.handle_agent_data_update_packet(&event.data) + } + crate::packets::PacketType::AvatarAnimation => { + self.handle_avatar_animation_packet(&event.data) + } + crate::packets::PacketType::MuteListUpdate => { + self.handle_mute_list_update_packet(&event.data, event.simulator) + } + crate::packets::PacketType::SendXferPacket => { + self.handle_mute_xfer_packet(&event.data, event.simulator) + } + _ => Ok(()), + }; + // Packet callbacks are intentionally fault-isolated, matching the C# + // event pipeline. Malformed wire data is rejected without panicking or + // poisoning the network receive worker. + let _ = result; + } + + fn handle_mute_list_update_packet( + &self, + data: &[u8], + simulator: Simulator, + ) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let expected_agent = *self + .agent_id + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if expected_agent != UUID::zero() && packet.mute_data.agent_id != expected_agent { + return Ok(()); + } + + let filename = wire_string(packet.mute_data.filename); + let xfer_id = UUID::random()?.get_u_long()?; + { + let mut transfers = mutex(&self.mute_xfers); + transfers.retain(|_, transfer| transfer.started.elapsed() < Duration::from_mins(1)); + transfers.insert(xfer_id, MuteXfer::default()); + } + + let mut request = crate::packets::RequestXferPacket::new_with_constructor()?; + request.xfer_id.id = xfer_id; + request.xfer_id.filename = filename.into_bytes(); + request.xfer_id.file_path = 4; + request.xfer_id.delete_on_completion = true; + request.xfer_id.use_big_packets = false; + request.xfer_id.v_file_id = UUID::zero(); + request.xfer_id.v_file_type = libremetaverse_types::AssetType::Unknown as i16; + send_simulator_packet( + &simulator, + crate::packets::PacketType::RequestXfer, + request.to_bytes_with_method()?, + )?; + + Ok(()) + } + + fn handle_mute_xfer_packet(&self, data: &[u8], simulator: Simulator) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let xfer_id = packet.xfer_id.id; + let packet_number = packet.xfer_id.packet & 0x0fff_ffff; + let is_last = packet.xfer_id.packet & 0x8000_0000 != 0; + let mut completed = None; + let confirmation; + { + let mut transfers = mutex(&self.mute_xfers); + let Some(transfer) = transfers.get_mut(&xfer_id) else { + return Ok(()); + }; + if packet_number != transfer.next_packet { + confirmation = transfer.next_packet.saturating_sub(1); + } else { + confirmation = packet_number; + let mut incoming = packet.data_packet.data; + if packet_number == 0 { + let size_bytes: [u8; 4] = incoming + .get(..4) + .ok_or(Error::InvalidOperation)? + .try_into() + .map_err(|_| Error::InvalidOperation)?; + let size = usize::try_from(u32::from_le_bytes(size_bytes)) + .map_err(|_| Error::Argument)?; + if size > MAX_MUTE_LIST_BYTES { + transfers.remove(&xfer_id); + return Err(Error::Argument); + } + transfer.bytes = Vec::with_capacity(size); + transfer.announced_size = Some(size); + incoming.drain(..4); + } + let size = transfer.announced_size.ok_or(Error::InvalidOperation)?; + if transfer.bytes.len().saturating_add(incoming.len()) > size { + transfers.remove(&xfer_id); + return Err(Error::InvalidOperation); + } + transfer.bytes.extend_from_slice(&incoming); + transfer.next_packet = transfer.next_packet.saturating_add(1); + if is_last { + if transfer.bytes.len() != size { + transfers.remove(&xfer_id); + return Err(Error::InvalidOperation); + } + completed = Some(std::mem::take(&mut transfer.bytes)); + transfers.remove(&xfer_id); + } + } + } + + let mut confirm = crate::packets::ConfirmXferPacketPacket::new_with_constructor()?; + confirm.xfer_id.id = xfer_id; + confirm.xfer_id.packet = confirmation; + send_simulator_packet( + &simulator, + crate::packets::PacketType::ConfirmXferPacket, + confirm.to_bytes_with_method()?, + )?; + + if let Some(bytes) = completed { + let parsed = parse_mute_list(&wire_string(bytes)); + *self + .mute_list + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = parsed; + self.mute_list_updated.emit(()); + } + Ok(()) + } + + fn handle_chat_packet(&self, data: &[u8], simulator: Simulator) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let chat = packet.chat_data; + self.chat_from_simulator.emit(ChatEventArgs::new( + simulator, + wire_string(chat.message), + chat_audible_level(chat.audible)?, + chat_type(chat.chat_type)?, + chat_source_type(chat.source_type)?, + wire_string(chat.from_name), + chat.source_id, + chat.owner_id, + chat.position, + )?); + Ok(()) + } + + fn handle_instant_message_packet( + &self, + data: &[u8], + simulator: Simulator, + ) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let block = packet.message_block; + if block.from_group { + self.group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .entry(block.id) + .or_default(); + } + self.instant_message.emit(InstantMessageEventArgs::new( + InstantMessage { + binary_bucket: block.binary_bucket, + dialog: instant_message_dialog(block.dialog)?, + from_agent_id: packet.agent_data.agent_id, + from_agent_name: wire_string(block.from_agent_name), + group_im: block.from_group, + im_session_id: block.id, + message: wire_string(block.message), + offline: instant_message_online(block.offline)?, + parent_estate_id: block.parent_estate_id, + position: block.position, + region_id: block.region_id, + timestamp: UNIX_EPOCH + Duration::from_secs(u64::from(block.timestamp)), + to_agent_id: block.to_agent_id, + }, + Some(simulator), + )?); + Ok(()) + } + + fn handle_money_balance_packet(&self, data: &[u8]) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let money = packet.money_data; + let transaction = packet.transaction_info; + *self + .balance + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = money.money_balance; + self.money_balance_reply + .emit(MoneyBalanceReplyEventArgs::new( + money.transaction_id, + money.transaction_success, + money.money_balance, + money.square_meters_credit, + money.square_meters_committed, + wire_string(money.description), + crate::TransactionInfo { + amount: transaction.amount, + dest_id: transaction.dest_id, + is_dest_group: transaction.is_dest_group, + is_source_group: transaction.is_source_group, + item_description: wire_string(transaction.item_description), + source_id: transaction.source_id, + transaction_type: transaction.transaction_type, + }, + )?); + self.money_balance + .emit(BalanceEventArgs::new(money.money_balance)?); + Ok(()) + } + + fn handle_agent_data_update_packet(&self, data: &[u8]) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let update = packet.agent_data; + let expected_id = self.expected_agent_id(); + if expected_id != UUID::zero() && update.agent_id != expected_id { + return Ok(()); + } + let first_name = wire_string(update.first_name); + let last_name = wire_string(update.last_name); + let group_title = wire_string(update.group_title); + let group_name = wire_string(update.group_name); + let group_powers = crate::GroupPowers(update.group_powers); + self.first_name + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone_from(&first_name); + self.last_name + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone_from(&last_name); + *self + .active_group + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = update.active_group_id; + *self + .active_group_powers + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = group_powers; + self.agent_data_reply.emit(AgentDataReplyEventArgs::new( + first_name, + last_name, + update.active_group_id, + group_title, + group_powers, + group_name, + )?); + Ok(()) + } + + fn handle_avatar_animation_packet(&self, data: &[u8]) -> Result<(), Error> { + let packet = decode_raw_packet::(data)?; + let expected_id = self.expected_agent_id(); + if expected_id != UUID::zero() && packet.sender.id != expected_id { + return Ok(()); + } + let animations = packet + .animation_list + .into_iter() + .map(|entry| (entry.anim_id, entry.anim_sequence_id)) + .collect::>(); + self.signaled_animations + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone_from(&animations); + self.animations_changed + .emit(AnimationsChangedEventArgs::new(animations)?); + Ok(()) + } + + fn expected_agent_id(&self) -> UUID { + let configured_id = *self + .agent_id + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if configured_id == UUID::zero() { + mutex(&self.network) + .as_ref() + .map_or(UUID::zero(), crate::NetworkManager::native_agent_id) + } else { + configured_id + } + } + + fn handle_caps_event( + self: &Arc, + client: Arc, + name: &str, + message: &dyn crate::interfaces::IMessage, + simulator: Simulator, + ) { + let result = match name { + "ChatterBoxInvitation" => self.handle_chat_invitation(client, message, simulator), + "ChatterBoxSessionEventReply" => self.handle_chat_session_event_reply(client, message), + "ChatterBoxSessionStartReply" => self.handle_chat_session_start_reply(message), + "ChatterBoxSessionAgentListUpdates" => self.handle_chat_session_agent_updates(message), + "SetDisplayNameReply" => self.handle_set_display_name_reply(message), + "NavMeshStatusUpdate" => self.handle_nav_mesh_status_update(message, simulator), + _ => Ok(()), + }; + // Event queue handlers are fault-isolated just like packet handlers. + // A malformed event must not stop delivery of subsequent queue entries. + let _ = result; + } + + fn handle_chat_session_event_reply( + &self, + client: Arc, + message: &dyn crate::interfaces::IMessage, + ) -> Result<(), Error> { + let mut reply = crate::messages::linden::ChatterboxSessionEventReplyMessage::new()?; + reply.deserialize(message.serialize()?)?; + if !reply.success { + AgentManager::native_new(Some(client))?.native_join_group_chat(reply.session_id)?; + } + Ok(()) + } + + fn handle_chat_session_start_reply( + &self, + message: &dyn crate::interfaces::IMessage, + ) -> Result<(), Error> { + let mut reply = crate::messages::linden::ChatterBoxSessionStartReplyMessage::new()?; + reply.deserialize(message.serialize()?)?; + if reply.success { + self.group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .entry(reply.session_id) + .or_default(); + } + self.group_chat_joined.emit(GroupChatJoinedEventArgs::new( + reply.session_id, + reply.session_name, + reply.temp_session_id, + reply.success, + )?); + Ok(()) + } + + fn handle_chat_session_agent_updates( + &self, + message: &dyn crate::interfaces::IMessage, + ) -> Result<(), Error> { + let mut update = crate::messages::linden::ChatterBoxSessionAgentListUpdatesMessage::new()?; + update.deserialize(message.serialize()?)?; + let session_id = update.session_id; + let mut added = Vec::new(); + let mut left = Vec::new(); + { + let mut sessions = self + .group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let members = sessions.entry(session_id).or_default(); + for block in update.updates { + let found = members + .iter() + .position(|member| member.avatar_key == block.agent_id); + match block.transition.as_str() { + "ENTER" if found.is_none() => added.push(block.agent_id), + "LEAVE" => { + if let Some(index) = found { + members.remove(index); + } + left.push(block.agent_id); + continue; + } + _ => {} + } + let member = ChatSessionMember { + avatar_key: block.agent_id, + can_voice_chat: block.can_voice_chat, + is_moderator: block.is_moderator, + mute_text: block.mute_text, + mute_voice: block.mute_voice, + }; + if let Some(index) = found { + members[index] = member; + } else if block.transition == "ENTER" { + members.push(member); + } + } + } + for agent_id in added { + self.chat_session_member_added + .emit(ChatSessionMemberAddedEventArgs::new(session_id, agent_id)?); + } + for agent_id in left { + self.chat_session_member_left + .emit(ChatSessionMemberLeftEventArgs::new(session_id, agent_id)?); + } + Ok(()) + } + + fn handle_chat_invitation( + self: &Arc, + client: Arc, + message: &dyn crate::interfaces::IMessage, + simulator: Simulator, + ) -> Result<(), Error> { + let mut invitation = crate::messages::linden::ChatterBoxInvitationMessage::new()?; + invitation.deserialize(message.serialize()?)?; + // The reference deliberately ignores voice-only invitations. + if invitation.voice { + return Ok(()); + } + self.group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .entry(invitation.im_session_id) + .or_default(); + let args = InstantMessageEventArgs::new( + InstantMessage { + binary_bucket: invitation.binary_bucket, + dialog: invitation.dialog, + from_agent_id: invitation.from_agent_id, + from_agent_name: invitation.from_agent_name, + group_im: invitation.group_im, + im_session_id: invitation.im_session_id, + message: invitation.message, + offline: invitation.offline, + parent_estate_id: invitation.parent_estate_id, + position: invitation.position, + region_id: invitation.region_id, + timestamp: invitation.timestamp, + to_agent_id: invitation.to_agent_id, + }, + Some(simulator), + )?; + let session_id = args.im().im_session_id; + let inner = Arc::clone(self); + let fallback_args = args.clone(); + let spawned = std::thread::Builder::new() + .name("libremetaverse-chat-invitation".into()) + .spawn(move || { + if let (Ok(runtime), Ok(manager)) = ( + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build(), + AgentManager::native_new(Some(client)), + ) { + // The C# handler catches acceptance failures and still + // delivers the invitation to subscribers afterwards. + let _ = runtime + .block_on(manager.native_chatter_box_accept_invite(session_id, None)); + } + inner.instant_message.emit(args); + }); + if spawned.is_err() { + self.instant_message.emit(fallback_args); + } + Ok(()) + } + + fn handle_set_display_name_reply( + &self, + message: &dyn crate::interfaces::IMessage, + ) -> Result<(), Error> { + let mut reply = crate::messages::linden::SetDisplayNameReplyMessage::new()?; + reply.deserialize(message.serialize()?)?; + self.set_display_name_reply + .emit(SetDisplayNameReplyEventArgs::new( + reply.status, + reply.reason, + reply.display_name, + )?); + Ok(()) + } + + fn handle_nav_mesh_status_update( + &self, + message: &dyn crate::interfaces::IMessage, + simulator: Simulator, + ) -> Result<(), Error> { + let mut update = crate::messages::linden::NavMeshStatusUpdateMessage::new()?; + update.deserialize(message.serialize()?)?; + *self + .last_nav_mesh_status + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(update.clone()); + self.nav_mesh_status_update + .emit(NavMeshStatusUpdateEventArgs::new(update, simulator)?); + Ok(()) + } +} + +/// Native owner for the non-movement `AgentManager` surface. +pub struct AgentManager { + pub agent_state_status: Option, + pub group_chat_sessions: Arc>>>, + pub movement: AgentManagerAgentMovement, + pub mute_list: Arc>>, + pub signaled_animations: Arc>>, + client: Arc, + inner: Arc, +} + +impl fmt::Debug for AgentManager { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("AgentManager") + .field("disposed", &self.inner.disposed.load(Ordering::Acquire)) + .field( + "group_chat_session_count", + &self + .group_chat_sessions + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .len(), + ) + .finish_non_exhaustive() + } +} + +impl AgentManager { + pub(crate) fn native_new(client: Option>) -> Result { + let client = client.ok_or(Error::ArgumentNull)?; + let inner = client.cached_agent_manager_inner().unwrap_or_else(|| { + let inner = Arc::new(AgentManagerInner::default()); + client.cache_agent_manager_inner(&inner); + inner + }); + let network = { + let mut network = mutex(&inner.network); + network.get_or_insert_with(|| client.network()).clone() + }; + { + let mut subscription = mutex(&inner.raw_packet_subscription); + if subscription.is_none() { + let weak = Arc::downgrade(&inner); + *subscription = Some(network.subscribe_raw_packet(Arc::new(move |event| { + if let Some(inner) = weak.upgrade() { + inner.handle_raw_packet(event); + } + }))); + } + } + { + let mut subscriptions = mutex(&inner.caps_subscriptions); + if subscriptions.is_empty() { + for event_name in [ + "ChatterBoxInvitation", + "ChatterBoxSessionEventReply", + "ChatterBoxSessionStartReply", + "ChatterBoxSessionAgentListUpdates", + "SetDisplayNameReply", + "NavMeshStatusUpdate", + ] { + let weak_inner = Arc::downgrade(&inner); + let weak_client = Arc::downgrade(&client); + subscriptions.push(network.subscribe_caps( + event_name.into(), + CapsEventQueueCallback::from_handler(move |name, message, simulator| { + if let (Some(inner), Some(client)) = + (weak_inner.upgrade(), weak_client.upgrade()) + { + inner.handle_caps_event(client, name, message, simulator); + } + }), + )); + } + } + } + Ok(Self { + agent_state_status: None, + group_chat_sessions: Arc::clone(&inner.group_chat_sessions), + movement: AgentManagerAgentMovement { + body_rotation: Quaternion::identity(), + camera: AgentManagerAgentMovementAgentCamera { far: 128.0 }, + flags: AgentFlags::NONE, + head_rotation: Quaternion::identity(), + state: AgentState::NONE, + }, + mute_list: Arc::clone(&inner.mute_list), + signaled_animations: Arc::clone(&inner.signaled_animations), + client, + inner, + }) + } + + pub(crate) fn native_subscribe_im( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.instant_message.subscribe(handler) + } + + pub(crate) fn native_subscribe_agent_data_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.agent_data_reply.subscribe(handler) + } + + pub(crate) fn native_subscribe_animations_changed( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.animations_changed.subscribe(handler) + } + + pub(crate) fn native_dispose(&self) -> Result<(), Error> { + if self.inner.disposed.swap(true, Ordering::AcqRel) { + return Ok(()); + } + mutex(&self.inner.raw_packet_subscription).take(); + mutex(&self.inner.caps_subscriptions).clear(); + mutex(&self.inner.network).take(); + mutex(&self.inner.mute_xfers).clear(); + self.group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + self.mute_list + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + self.inner + .active_gestures + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + self.signaled_animations + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clear(); + Ok(()) + } + + pub(crate) fn native_subscribe_chat_from_simulator( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.chat_from_simulator.subscribe(handler) + } + + pub(crate) fn native_subscribe_money_balance( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.money_balance.subscribe(handler) + } + + pub(crate) fn native_subscribe_money_balance_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.money_balance_reply.subscribe(handler) + } + + pub(crate) fn native_subscribe_agent_experiences_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.agent_experiences_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_experience_preferences_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.experience_preferences_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_region_experiences_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.region_experiences_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_experience_info_received( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.experience_info_received.subscribe(handler) + } + + pub(crate) fn native_subscribe_agent_preferences_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.agent_preferences_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_avatar_render_info_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.avatar_render_info_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_viewer_benefits_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.viewer_benefits_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_product_info_updated( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.product_info_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_mute_list_updated( + &self, + handler: EventHandler<()>, + ) -> Subscription { + self.inner.mute_list_updated.subscribe(handler) + } + + pub(crate) fn native_subscribe_group_chat_joined( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.group_chat_joined.subscribe(handler) + } + + pub(crate) fn native_subscribe_chat_session_member_added( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.chat_session_member_added.subscribe(handler) + } + + pub(crate) fn native_subscribe_chat_session_member_left( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.chat_session_member_left.subscribe(handler) + } + + pub(crate) fn native_subscribe_set_display_name_reply( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.set_display_name_reply.subscribe(handler) + } + + pub(crate) fn native_subscribe_nav_mesh_status_update( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.nav_mesh_status_update.subscribe(handler) + } + + pub(crate) fn native_last_nav_mesh_status( + &self, + ) -> Option { + self.inner + .last_nav_mesh_status + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_last_nav_mesh_status( + &self, + value: Option, + ) { + *self + .inner + .last_nav_mesh_status + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_agent_experience_list(&self) -> Option { + self.inner + .agent_experiences + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_agent_experience_list(&self, value: Option) { + *self + .inner + .agent_experiences + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_experience_preferences(&self) -> Option { + self.inner + .experience_preferences + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_experience_preferences( + &self, + value: Option, + ) { + *self + .inner + .experience_preferences + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_last_region_experiences(&self) -> Option { + self.inner + .region_experiences + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_last_region_experiences( + &self, + value: Option, + ) { + *self + .inner + .region_experiences + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_avatar_render_info(&self) -> Option { + self.inner + .avatar_render_info + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_avatar_render_info(&self, value: Option) { + *self + .inner + .avatar_render_info + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_viewer_benefits(&self) -> Option { + self.inner + .viewer_benefits + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_benefits(&self) -> crate::AccountLevelBenefits { + self.inner + .benefits + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + .or_else(|| self.client.network().native_account_level_benefits()) + .unwrap_or_else(|| { + crate::AccountLevelBenefits::new_with_osd_map( + OSDMap::new_with_dictionary(HashMap::new()).expect("an empty OSD map is valid"), + ) + .expect("an empty benefit map is valid") + }) + } + + pub(crate) fn native_set_benefits(&self, value: crate::AccountLevelBenefits) { + *self + .inner + .benefits + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value); + } + + pub(crate) fn native_set_viewer_benefits(&self, value: Option) { + *self + .inner + .viewer_benefits + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_product_info(&self) -> Option { + self.inner + .product_info + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_product_info(&self, value: Option) { + *self + .inner + .product_info + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_first_name(&self) -> String { + self.inner + .first_name + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_first_name(&self, value: String) { + *self + .inner + .first_name + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_last_name(&self) -> String { + self.inner + .last_name + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_active_group(&self) -> UUID { + *self + .inner + .active_group + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + pub(crate) fn native_set_active_group(&self, value: UUID) { + *self + .inner + .active_group + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_active_group_powers(&self) -> crate::GroupPowers { + *self + .inner + .active_group_powers + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + pub(crate) fn native_set_active_group_powers(&self, value: crate::GroupPowers) { + *self + .inner + .active_group_powers + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_set_last_name(&self, value: String) { + *self + .inner + .last_name + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_agent_access(&self) -> String { + self.inner + .agent_access + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + + pub(crate) fn native_set_agent_access(&self, value: String) { + *self + .inner + .agent_access + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_hover_height(&self) -> f32 { + *self + .inner + .hover_height + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + pub(crate) fn native_set_hover_height(&self, value: f32) { + *self + .inner + .hover_height + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_balance(&self) -> i32 { + *self + .inner + .balance + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + } + + pub(crate) fn native_set_balance(&self, value: i32) { + *self + .inner + .balance + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_agent_id(&self) -> UUID { + let value = *self + .inner + .agent_id + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if value == UUID::zero() { + self.client.network().native_agent_id() + } else { + value + } + } + + pub(crate) fn native_set_agent_id(&self, value: UUID) { + *self + .inner + .agent_id + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + pub(crate) fn native_session_id(&self) -> UUID { + let value = *self + .inner + .session_id + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if value == UUID::zero() { + self.client.network().native_session_id() + } else { + value + } + } + + pub(crate) fn native_set_session_id(&self, value: UUID) { + *self + .inner + .session_id + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = value; + } + + fn send_packet( + &self, + packet_type: crate::packets::PacketType, + data: Vec, + ) -> Result<(), Error> { + let length = i32::try_from(data.len()).map_err(|_| Error::Argument)?; + let simulator = self + .client + .network() + .current_sim() + .ok_or(Error::InvalidOperation)?; + let do_zerocode = data + .first() + .is_some_and(|flags| flags & crate::Helpers::MSG_ZEROCODED != 0); + simulator.native_send_packet_data(data, length, packet_type, do_zerocode) + } + + pub(crate) fn native_chat( + &self, + message: String, + channel: i32, + type_: ChatType, + split_large_messages: Option, + ) -> Result<(), Error> { + for (packet_type, data) in + self.native_chat_payloads(message, channel, type_, split_large_messages)? + { + self.send_packet(packet_type, data)?; + } + Ok(()) + } + + fn native_chat_payloads( + &self, + message: String, + channel: i32, + type_: ChatType, + split_large_messages: Option, + ) -> Result)>, Error> { + if channel < 0 { + let message = split_utf8(&message, MAX_DIALOG_LABEL_BYTES, 1) + .into_iter() + .next() + .unwrap_or_default(); + let mut packet = crate::packets::ScriptDialogReplyPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.object_id = self.native_agent_id(); + packet.data.chat_channel = channel; + packet.data.button_index = 0; + packet.data.button_label = message.into_bytes(); + return Ok(vec![( + crate::packets::PacketType::ScriptDialogReply, + packet.to_bytes_with_method()?, + )]); + } + + let max_parts = if split_large_messages.unwrap_or(true) { + usize::MAX + } else { + 1 + }; + split_utf8(&message, MAX_CHAT_BYTES, max_parts) + .into_iter() + .map(|message| { + let mut packet = crate::packets::ChatFromViewerPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.chat_data.channel = channel; + packet.chat_data.message = message.into_bytes(); + packet.chat_data.type_ = type_ as u8; + Ok(( + crate::packets::PacketType::ChatFromViewer, + packet.to_bytes_with_method()?, + )) + }) + .collect() + } + + pub(crate) fn native_instant_message( + &self, + target: UUID, + message: String, + session_id: Option, + ) -> Result<(), Error> { + let session_id = session_id.unwrap_or_else(|| { + if self.native_agent_id() == target { + self.native_agent_id() + } else { + UUID::bitxor(target, self.native_agent_id()) + } + }); + self.native_instant_message_full( + self.native_name(), + target, + message, + session_id, + InstantMessageDialog::MessageFromAgent, + InstantMessageOnline::Offline, + Vector3::zero(), + UUID::zero(), + Vec::new(), + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn native_instant_message_full( + &self, + from_name: String, + target: UUID, + message: String, + mut im_session_id: UUID, + dialog: InstantMessageDialog, + offline: InstantMessageOnline, + _position: Vector3, + region_id: UUID, + binary_bucket: Vec, + ) -> Result<(), Error> { + // Match the reference's safety guard. Message text is intentionally not logged. + if target == UUID::zero() { + return Ok(()); + } + if im_session_id == UUID::zero() || im_session_id == self.native_agent_id() { + im_session_id = if self.native_agent_id() == target { + self.native_agent_id() + } else { + UUID::bitxor(target, self.native_agent_id()) + }; + } + for message in split_utf8(&message, MAX_CHAT_BYTES, usize::MAX) { + let mut packet = crate::packets::ImprovedInstantMessagePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.message_block.dialog = dialog as u8; + packet.message_block.from_agent_name = from_name.as_bytes().to_vec(); + packet.message_block.from_group = false; + packet.message_block.id = im_session_id; + packet.message_block.message = message.into_bytes(); + packet.message_block.offline = offline as u8; + packet.message_block.to_agent_id = target; + packet + .message_block + .binary_bucket + .clone_from(&binary_bucket); + // The C# implementation ignores its position argument and sends zero. + packet.message_block.position = Vector3::zero(); + packet.message_block.region_id = region_id; + self.send_packet( + crate::packets::PacketType::ImprovedInstantMessage, + packet.to_bytes_with_method()?, + )?; + } + Ok(()) + } + + pub(crate) fn native_instant_message_conference( + &self, + from_name: String, + target: UUID, + message: String, + session_id: UUID, + conference_ids: Vec, + ) -> Result<(), Error> { + let mut bucket = Vec::with_capacity(conference_ids.len().saturating_mul(16)); + for id in conference_ids { + bucket.extend_from_slice(&id.get_bytes()?); + } + self.native_instant_message_full( + from_name, + target, + message, + session_id, + InstantMessageDialog::MessageFromAgent, + InstantMessageOnline::Offline, + Vector3::zero(), + UUID::zero(), + bucket, + ) + } + + pub(crate) fn native_instant_message_group( + &self, + from_name: Option, + group_id: UUID, + message: String, + ) -> Result<(), Error> { + if !self + .group_chat_sessions + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .contains_key(&group_id) + { + return Ok(()); + } + let from_name = from_name.unwrap_or_else(|| self.native_name()); + self.native_instant_message_full( + from_name, + group_id, + message, + group_id, + InstantMessageDialog::SessionSend, + InstantMessageOnline::Online, + Vector3::zero(), + UUID::zero(), + vec![0], + ) + } + + pub(crate) fn native_is_group_message(&self, message: InstantMessage) -> bool { + message.group_im + || self + .group_chat_sessions + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .contains_key(&message.im_session_id) + } + + pub(crate) fn native_group_invite_respond( + &self, + group_id: UUID, + im_session_id: UUID, + accept: bool, + ) -> Result<(), Error> { + self.native_instant_message_full( + self.native_name(), + group_id, + String::new(), + im_session_id, + if accept { + InstantMessageDialog::GroupInvitationAccept + } else { + InstantMessageDialog::GroupInvitationDecline + }, + InstantMessageOnline::Offline, + Vector3::zero(), + UUID::zero(), + Vec::new(), + ) + } + + pub(crate) fn native_join_group_chat(&self, group_id: UUID) -> Result<(), Error> { + self.send_session_control(group_id, InstantMessageDialog::SessionGroupStart) + } + + pub(crate) fn native_leave_group_chat(&self, group_id: UUID) -> Result<(), Error> { + self.send_session_control(group_id, InstantMessageDialog::SessionDrop)?; + self.group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .remove(&group_id); + Ok(()) + } + + fn send_session_control( + &self, + group_id: UUID, + dialog: InstantMessageDialog, + ) -> Result<(), Error> { + let mut packet = crate::packets::ImprovedInstantMessagePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.message_block.dialog = dialog as u8; + packet.message_block.from_agent_name = self.native_name().into_bytes(); + packet.message_block.id = group_id; + packet.message_block.to_agent_id = group_id; + packet.message_block.position = Vector3::zero(); + self.send_packet( + crate::packets::PacketType::ImprovedInstantMessage, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_reply_to_script_dialog( + &self, + channel: i32, + button_index: i32, + button_label: String, + object_id: UUID, + ) -> Result<(), Error> { + let mut packet = crate::packets::ScriptDialogReplyPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.chat_channel = channel; + packet.data.button_index = button_index; + packet.data.button_label = button_label.into_bytes(); + packet.data.object_id = object_id; + self.send_packet( + crate::packets::PacketType::ScriptDialogReply, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_activate_gesture( + &self, + inventory_id: UUID, + asset_id: UUID, + ) -> Result<(), Error> { + let mut packet = crate::packets::ActivateGesturesPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + let mut block = crate::packets::ActivateGesturesPacketDataBlock::new_with_constructor()?; + block.item_id = inventory_id; + block.asset_id = asset_id; + packet.data = vec![block]; + self.send_packet( + crate::packets::PacketType::ActivateGestures, + packet.to_bytes_with_method()?, + )?; + self.inner + .active_gestures + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .insert(inventory_id, asset_id); + Ok(()) + } + + pub(crate) fn native_deactivate_gesture(&self, inventory_id: UUID) -> Result<(), Error> { + let mut packet = crate::packets::DeactivateGesturesPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + let mut block = crate::packets::DeactivateGesturesPacketDataBlock::new_with_constructor()?; + block.item_id = inventory_id; + packet.data = vec![block]; + self.send_packet( + crate::packets::PacketType::DeactivateGestures, + packet.to_bytes_with_method()?, + )?; + self.inner + .active_gestures + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .remove(&inventory_id); + Ok(()) + } + + pub(crate) async fn native_play_gesture( + &self, + gesture_id: UUID, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + tokio::select! { + result = self.play_gesture_inner(gesture_id, cancellation.clone()) => result, + () = cancellation.cancelled() => Err(Error::Cancelled), + () = tokio::time::sleep(GESTURE_TIMEOUT) => Err(Error::Cancelled), + } + } + + async fn play_gesture_inner( + &self, + gesture_id: UUID, + cancellation: libremetaverse_types::compat::CancellationToken, + ) -> Result<(), Error> { + let Some(data) = self + .fetch_gesture_asset(gesture_id, cancellation.clone()) + .await? + else { + return Ok(()); + }; + let mut gesture = crate::assets::AssetGesture::new_with_uuid_bytes(gesture_id, data)?; + if !gesture.decode()? { + return Ok(()); + } + for step in gesture.sequence { + cancellation.throw_if_cancellation_requested()?; + match step { + crate::assets::GestureStep::Chat(step) => { + let (channel, text) = gesture_chat_channel(&step.text); + self.native_chat(text, channel, ChatType::Normal, None)?; + } + crate::assets::GestureStep::Animation(step) => { + if step.animation_start { + if self + .signaled_animations + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .contains_key(&step.id) + { + self.native_animation_stop(step.id, true)?; + } + self.native_animation_start(step.id, true)?; + } else { + self.native_animation_stop(step.id, true)?; + } + } + crate::assets::GestureStep::Sound(step) => { + self.native_play_gesture_sound(step.id)?; + } + crate::assets::GestureStep::Wait(step) => { + if step.wait_for_time { + let seconds = f64::from(step.wait_time.max(0.0)); + tokio::select! { + () = tokio::time::sleep(Duration::from_secs_f64(seconds)) => {}, + () = cancellation.cancelled() => return Err(Error::Cancelled), + } + } + // Preserve the reference behavior: WaitForAnimation is + // recognized but intentionally has no blocking behavior. + } + crate::assets::GestureStep::EOF(_) => break, + } + } + Ok(()) + } + + async fn fetch_gesture_asset( + &self, + gesture_id: UUID, + cancellation: libremetaverse_types::compat::CancellationToken, + ) -> Result>, Error> { + let simulator = self + .client + .network() + .current_sim() + .ok_or(Error::InvalidOperation)?; + if let Some(viewer_asset) = simulator + .native_caps() + .and_then(|caps| caps.capability_uri("ViewerAsset".into()).ok().flatten()) + { + let separator = if viewer_asset.0.contains('?') { + '&' + } else { + '?' + }; + let uri = Uri(format!( + "{}{separator}gesture_id={gesture_id}", + viewer_asset.0 + )); + let response = self + .client + .http_caps_client() + .get(uri, cancellation, None) + .await; + return match response { + Ok((response, data)) if response.is_success_status_code() => Ok(Some(data)), + Err(Error::Cancelled) => Err(Error::Cancelled), + Ok(_) | Err(_) => Ok(None), + }; + } + self.fetch_gesture_asset_udp(simulator, gesture_id, cancellation) + .await + } + + async fn fetch_gesture_asset_udp( + &self, + simulator: Simulator, + gesture_id: UUID, + cancellation: libremetaverse_types::compat::CancellationToken, + ) -> Result>, Error> { + let transfer_id = UUID::random()?; + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); + let _subscription = self + .client + .network() + .subscribe_raw_packet(Arc::new(move |event| { + if matches!( + event.packet_type, + crate::packets::PacketType::TransferInfo + | crate::packets::PacketType::TransferPacket + ) { + let _ = sender.send(event); + } + })); + let mut request = crate::packets::TransferRequestPacket::new_with_constructor()?; + request.transfer_info.channel_type = crate::ChannelType::Asset as i32; + request.transfer_info.priority = 101.0; + request.transfer_info.source_type = crate::SourceType::Asset as i32; + request.transfer_info.transfer_id = transfer_id; + request.transfer_info.params = gesture_id.get_bytes()?; + request + .transfer_info + .params + .extend_from_slice(&(libremetaverse_types::AssetType::Gesture as i32).to_le_bytes()); + let data = request.to_bytes_with_method()?; + let length = i32::try_from(data.len()).map_err(|_| Error::Argument)?; + simulator.native_send_packet_data( + data, + length, + crate::packets::PacketType::TransferRequest, + false, + )?; + let mut abort = UdpAssetAbortGuard::new(simulator, transfer_id); + let mut size = None; + let mut chunks = BTreeMap::>::new(); + loop { + let event = tokio::select! { + event = receiver.recv() => event.ok_or(Error::InvalidOperation)?, + () = cancellation.cancelled() => return Err(Error::Cancelled), + }; + match event.packet_type { + crate::packets::PacketType::TransferInfo => { + let packet = + decode_raw_packet::(&event.data)?; + if packet.transfer_info.transfer_id != transfer_id { + continue; + } + if packet.transfer_info.status != crate::StatusCode::OK as i32 { + abort.complete(); + return Ok(None); + } + let announced = + usize::try_from(packet.transfer_info.size).map_err(|_| Error::Argument)?; + if announced > MAX_GESTURE_ASSET_BYTES { + return Err(Error::Argument); + } + size = Some(announced); + } + crate::packets::PacketType::TransferPacket => { + let packet = + decode_raw_packet::(&event.data)?; + if packet.transfer_data.transfer_id != transfer_id + || packet.transfer_data.packet < 0 + { + continue; + } + chunks + .entry(packet.transfer_data.packet) + .or_insert(packet.transfer_data.data); + } + _ => continue, + } + let Some(expected_size) = size else { + continue; + }; + let mut assembled = Vec::with_capacity(expected_size); + for packet_number in 0_i32.. { + let Some(chunk) = chunks.get(&packet_number) else { + break; + }; + let remaining = expected_size.saturating_sub(assembled.len()); + assembled.extend_from_slice(&chunk[..chunk.len().min(remaining)]); + if assembled.len() == expected_size { + abort.complete(); + return Ok(Some(assembled)); + } + } + if expected_size == 0 { + abort.complete(); + return Ok(Some(Vec::new())); + } + } + } + + fn native_play_gesture_sound(&self, sound_id: UUID) -> Result<(), Error> { + if sound_id == UUID::zero() { + return Ok(()); + } + let simulator = self + .client + .network() + .current_sim() + .ok_or(Error::InvalidOperation)?; + let mut packet = crate::packets::SoundTriggerPacket::new_with_constructor()?; + packet.sound_data.sound_id = sound_id; + packet.sound_data.handle = simulator.handle; + packet.sound_data.position = Vector3::zero(); + packet.sound_data.gain = 1.0; + self.send_packet( + crate::packets::PacketType::SoundTrigger, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_active_gestures(&self) -> RwLock> { + RwLock::new( + self.inner + .active_gestures + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(), + ) + } + + pub(crate) fn native_request_balance(&self) -> Result<(), Error> { + let mut packet = crate::packets::MoneyBalanceRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.money_data.transaction_id = UUID::zero(); + self.send_packet( + crate::packets::PacketType::MoneyBalanceRequest, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_animation_start( + &self, + animation: UUID, + reliable: bool, + ) -> Result<(), Error> { + self.native_animate(HashMap::from([(animation, true)]), reliable) + } + + pub(crate) fn native_animation_stop( + &self, + animation: UUID, + reliable: bool, + ) -> Result<(), Error> { + self.native_animate(HashMap::from([(animation, false)]), reliable) + } + + pub(crate) fn native_animate( + &self, + animations: HashMap, + reliable: bool, + ) -> Result<(), Error> { + let mut packet = crate::packets::AgentAnimationPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.animation_list = animations + .iter() + .map(|(animation, start)| { + let mut block = + crate::packets::AgentAnimationPacketAnimationListBlock::new_with_constructor()?; + block.anim_id = *animation; + block.start_anim = *start; + Ok(block) + }) + .collect::, Error>>()?; + packet.physical_avatar_event_list = Vec::new(); + let mut data = packet.to_bytes_with_method()?; + let flags = data.first_mut().ok_or(Error::Argument)?; + if reliable { + *flags |= crate::Helpers::MSG_RELIABLE; + } else { + *flags &= !crate::Helpers::MSG_RELIABLE; + } + self.send_packet(crate::packets::PacketType::AgentAnimation, data)?; + let mut signaled = self + .signaled_animations + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner); + for (animation, start) in animations { + if start { + signaled.insert(animation, 1); + } else { + signaled.remove(&animation); + } + } + Ok(()) + } + + pub(crate) fn native_request_mute_list(&self) -> Result<(), Error> { + let mut packet = crate::packets::MuteListRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.mute_data.mute_crc = 0; + self.send_packet( + crate::packets::PacketType::MuteListRequest, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_update_mute_list_entry( + &self, + type_: crate::MuteType, + id: UUID, + name: String, + flags: crate::MuteFlags, + ) -> Result<(), Error> { + let mut packet = crate::packets::UpdateMuteListEntryPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.mute_data.mute_type = type_ as i32; + packet.mute_data.mute_id = id; + packet.mute_data.mute_name = name.as_bytes().to_vec(); + packet.mute_data.mute_flags = u32::try_from(flags.0).map_err(|_| Error::Argument)?; + self.send_packet( + crate::packets::PacketType::UpdateMuteListEntry, + packet.to_bytes_with_method()?, + )?; + self.mute_list + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .insert( + format!("{id}|{name}"), + MuteEntry { + flags, + id, + name, + type_, + }, + ); + self.inner.mute_list_updated.emit(()); + Ok(()) + } + + pub(crate) fn native_remove_mute_list_entry( + &self, + id: UUID, + name: String, + ) -> Result<(), Error> { + let mut packet = crate::packets::RemoveMuteListEntryPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.mute_data.mute_id = id; + packet.mute_data.mute_name = name.as_bytes().to_vec(); + self.send_packet( + crate::packets::PacketType::RemoveMuteListEntry, + packet.to_bytes_with_method()?, + )?; + if self + .mute_list + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .remove(&format!("{id}|{name}")) + .is_some() + { + self.inner.mute_list_updated.emit(()); + } + Ok(()) + } + + pub(crate) fn native_give_money( + &self, + target: UUID, + amount: i32, + description: String, + type_: MoneyTransactionType, + flags: TransactionFlags, + ) -> Result<(), Error> { + let mut packet = crate::packets::MoneyTransferRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.money_data.description = description.into_bytes(); + packet.money_data.dest_id = target; + packet.money_data.source_id = self.native_agent_id(); + packet.money_data.transaction_type = type_ as i32; + packet.money_data.flags = flags.0; + packet.money_data.amount = amount; + self.send_packet( + crate::packets::PacketType::MoneyTransferRequest, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_give_avatar_money( + &self, + target: UUID, + amount: i32, + description: String, + ) -> Result<(), Error> { + self.native_give_money( + target, + amount, + description, + MoneyTransactionType::Gift, + TransactionFlags::NONE, + ) + } + + pub(crate) fn native_give_group_money( + &self, + target: UUID, + amount: i32, + description: String, + ) -> Result<(), Error> { + self.native_give_money( + target, + amount, + description, + MoneyTransactionType::Gift, + TransactionFlags::DEST_GROUP, + ) + } + + pub(crate) fn native_give_object_money( + &self, + target: UUID, + amount: i32, + object_name: String, + ) -> Result<(), Error> { + self.native_give_money( + target, + amount, + object_name, + MoneyTransactionType::PayObject, + TransactionFlags::NONE, + ) + } + + pub(crate) fn native_pay_upload_fee( + &self, + amount: Option, + description: String, + ) -> Result<(), Error> { + let benefit_cost = self.native_viewer_benefits().map_or(0, |benefits| { + benefits.account_level_benefits.texture_upload_cost + }); + self.native_give_money( + UUID::zero(), + amount.unwrap_or_else(|| { + if benefit_cost > 0 { + benefit_cost + } else { + self.client.settings_ref().upload_cost + } + }), + description, + MoneyTransactionType::UploadCharge, + TransactionFlags::NONE, + ) + } + + pub(crate) fn native_viewer_effect( + &self, + source_avatar: UUID, + target_object: UUID, + global_offset: Vector3d, + color: Color4, + duration: f32, + effect_id: UUID, + effect_type: EffectType, + subtype: Option, + ) -> Result<(), Error> { + let mut type_data = Vec::with_capacity(if subtype.is_some() { 57 } else { 56 }); + type_data.extend_from_slice(&source_avatar.get_bytes()?); + type_data.extend_from_slice(&target_object.get_bytes()?); + type_data.extend_from_slice(&global_offset.get_bytes()?); + if let Some(subtype) = subtype { + type_data.push(subtype); + } + let mut packet = crate::packets::ViewerEffectPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + let mut effect = crate::packets::ViewerEffectPacketEffectBlock::new_with_constructor()?; + effect.agent_id = self.native_agent_id(); + effect.color = color.get_bytes_with_method()?; + effect.duration = duration; + effect.id = effect_id; + effect.type_ = effect_type as u8; + effect.type_data = type_data; + packet.effect = vec![effect]; + self.send_packet( + crate::packets::PacketType::ViewerEffect, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_point_at_effect( + &self, + source_avatar: UUID, + target_object: UUID, + global_offset: Vector3d, + type_: PointAtType, + effect_id: UUID, + ) -> Result<(), Error> { + let duration = if type_ == PointAtType::Clear { + 0.0 + } else { + f32::MAX / 4.0 + }; + self.native_viewer_effect( + source_avatar, + target_object, + global_offset, + Color4::default(), + duration, + effect_id, + EffectType::PointAt, + Some(type_ as u8), + ) + } + + pub(crate) fn native_look_at_effect( + &self, + source_avatar: UUID, + target_object: UUID, + global_offset: Vector3d, + type_: LookAtType, + effect_id: UUID, + ) -> Result<(), Error> { + let duration = match type_ { + LookAtType::Clear | LookAtType::FreeLook => 2.0, + LookAtType::Hover => 1.0, + LookAtType::Idle => 3.0, + LookAtType::AutoListen | LookAtType::Respond => 4.0, + LookAtType::None | LookAtType::Select | LookAtType::Focus | LookAtType::Mouselook => { + f32::MAX / 2.0 + } + LookAtType::Conversation => 0.0, + }; + self.native_viewer_effect( + source_avatar, + target_object, + global_offset, + Color4::default(), + duration, + effect_id, + EffectType::LookAt, + Some(type_ as u8), + ) + } + + pub(crate) fn native_name(&self) -> String { + let first = self.native_first_name(); + let last = self.native_last_name(); + match (first.is_empty(), last.is_empty()) { + (false, false) => format!("{first} {last}"), + (false, true) => first, + (true, false) => last, + (true, true) => String::new(), + } + } + + pub(crate) fn native_update_profile_udp( + &self, + profile: crate::AvatarAvatarProperties, + ) -> Result<(), Error> { + let mut packet = crate::packets::AvatarPropertiesUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.properties_data.about_text = profile.about_text.into_bytes(); + packet.properties_data.allow_publish = + profile.flags.0 & crate::ProfileFlags::ALLOW_PUBLISH.0 != 0; + packet.properties_data.fl_about_text = profile.first_life_text.into_bytes(); + packet.properties_data.fl_image_id = profile.first_life_image; + packet.properties_data.image_id = profile.profile_image; + packet.properties_data.mature_publish = + profile.flags.0 & crate::ProfileFlags::MATURE_PUBLISH.0 != 0; + packet.properties_data.profile_url = profile.profile_url.into_bytes(); + self.send_packet( + crate::packets::PacketType::AvatarPropertiesUpdate, + packet.to_bytes_with_method()?, + ) + } + + fn profile_payload(profile: &crate::AvatarAvatarProperties) -> Result { + OSDMap::new_with_dictionary(HashMap::from([ + ( + "sl_about_text".into(), + OSD::String(profile.about_text.clone()), + ), + ( + "fl_about_text".into(), + OSD::String(profile.first_life_text.clone()), + ), + ( + "profile_url".into(), + OSD::String(profile.profile_url.clone()), + ), + ( + "allow_publish".into(), + OSD::Boolean(profile.flags.0 & crate::ProfileFlags::ALLOW_PUBLISH.0 != 0), + ), + ("sl_image_id".into(), OSD::UUID(profile.profile_image)), + ("fl_image_id".into(), OSD::UUID(profile.first_life_image)), + ])) + } + + async fn put_agent_profile( + &self, + target: UUID, + payload: OSDMap, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let Some(capability) = self.native_capability("AgentProfile", &cancellation)? else { + return Ok(()); + }; + let uri = Uri(format!("{}/{}", capability.0.trim_end_matches('/'), target)); + let (response, _) = self + .client + .http_caps_client() + .put_with_uri_osd_format_osd_cancellation_token_i_progress( + uri, + libremetaverse_structured_data::OSDFormat::Xml, + OSD::Map(payload.snapshot()), + cancellation, + None, + ) + .await?; + if response.is_success_status_code() { + Ok(()) + } else { + Err(Error::InvalidOperation) + } + } + + pub(crate) async fn native_update_profile_async( + &self, + profile: crate::AvatarAvatarProperties, + cancellation_token: Option, + ) -> Result<(), Error> { + self.put_agent_profile( + self.native_agent_id(), + Self::profile_payload(&profile)?, + cancellation_token, + ) + .await + } + + pub(crate) fn native_update_profile( + &self, + profile: crate::AvatarAvatarProperties, + ) -> Result<(), Error> { + let capability_available = self + .client + .network() + .current_sim() + .and_then(|simulator| simulator.native_caps()) + .and_then(|caps| caps.capability_uri("AgentProfile".into()).ok().flatten()) + .is_some(); + if !capability_available { + return self.native_update_profile_udp(profile); + } + let client = Arc::clone(&self.client); + let target = self.native_agent_id(); + let payload = Self::profile_payload(&profile)?; + std::thread::Builder::new() + .name("libremetaverse-profile-update".into()) + .spawn(move || { + let Ok(runtime) = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + else { + return; + }; + let Ok(manager) = AgentManager::native_new(Some(client)) else { + return; + }; + let _ = runtime.block_on(manager.put_agent_profile(target, payload, None)); + }) + .map(|_| ()) + .map_err(|_| Error::InvalidOperation) + } + + fn native_update_profile_notes_udp(&self, target: UUID, notes: String) -> Result<(), Error> { + let mut packet = crate::packets::AvatarNotesUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.target_id = target; + packet.data.notes = notes.into_bytes(); + self.send_packet( + crate::packets::PacketType::AvatarNotesUpdate, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) async fn native_update_profile_notes_async( + &self, + target: UUID, + notes: String, + cancellation_token: Option, + ) -> Result<(), Error> { + self.put_agent_profile( + target, + OSDMap::new_with_dictionary(HashMap::from([("notes".into(), OSD::String(notes))]))?, + cancellation_token, + ) + .await + } + + pub(crate) fn native_update_profile_notes( + &self, + target: UUID, + notes: String, + ) -> Result<(), Error> { + let capability_available = self + .client + .network() + .current_sim() + .and_then(|simulator| simulator.native_caps()) + .and_then(|caps| caps.capability_uri("AgentProfile".into()).ok().flatten()) + .is_some(); + if !capability_available { + return self.native_update_profile_notes_udp(target, notes); + } + let client = Arc::clone(&self.client); + let payload = + OSDMap::new_with_dictionary(HashMap::from([("notes".into(), OSD::String(notes))]))?; + std::thread::Builder::new() + .name("libremetaverse-profile-notes-update".into()) + .spawn(move || { + let Ok(runtime) = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + else { + return; + }; + let Ok(manager) = AgentManager::native_new(Some(client)) else { + return; + }; + let _ = runtime.block_on(manager.put_agent_profile(target, payload, None)); + }) + .map(|_| ()) + .map_err(|_| Error::InvalidOperation) + } + + pub(crate) fn native_update_interests( + &self, + interests: crate::AvatarInterests, + ) -> Result<(), Error> { + let mut packet = crate::packets::AvatarInterestsUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.properties_data.languages_text = interests.languages_text.into_bytes(); + packet.properties_data.skills_mask = interests.skills_mask; + packet.properties_data.skills_text = interests.skills_text.into_bytes(); + packet.properties_data.want_to_mask = interests.want_to_mask; + packet.properties_data.want_to_text = interests.want_to_text.into_bytes(); + self.send_packet( + crate::packets::PacketType::AvatarInterestsUpdate, + packet.to_bytes_with_method()?, + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn native_pick_info_update( + &self, + pick_id: UUID, + top_pick: bool, + parcel_id: UUID, + name: String, + global_position: Vector3d, + texture_id: UUID, + description: String, + ) -> Result<(), Error> { + let mut packet = crate::packets::PickInfoUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.pick_id = pick_id; + packet.data.desc = description.into_bytes(); + packet.data.creator_id = self.native_agent_id(); + packet.data.top_pick = top_pick; + packet.data.parcel_id = parcel_id; + packet.data.name = name.into_bytes(); + packet.data.snapshot_id = texture_id; + packet.data.pos_global = global_position; + packet.data.sort_order = 0; + packet.data.enabled = false; + self.send_packet( + crate::packets::PacketType::PickInfoUpdate, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_pick_delete(&self, pick_id: UUID) -> Result<(), Error> { + let mut packet = crate::packets::PickDeletePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.pick_id = pick_id; + self.send_packet( + crate::packets::PacketType::PickDelete, + packet.to_bytes_with_method()?, + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn native_update_classified_info( + &self, + classified_id: UUID, + category: crate::DirectoryManagerClassifiedCategories, + snapshot_id: UUID, + price: i32, + position: Vector3d, + name: String, + description: String, + auto_renew: bool, + ) -> Result<(), Error> { + let mut packet = crate::packets::ClassifiedInfoUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.classified_id = classified_id; + packet.data.category = category as u32; + packet.data.parcel_id = UUID::zero(); + packet.data.parent_estate = 0; + packet.data.snapshot_id = snapshot_id; + packet.data.pos_global = position; + packet.data.classified_flags = if auto_renew { 32 } else { 0 }; + packet.data.price_for_listing = price; + packet.data.name = name.into_bytes(); + packet.data.desc = description.into_bytes(); + self.send_packet( + crate::packets::PacketType::ClassifiedInfoUpdate, + packet.to_bytes_with_method()?, + ) + } + + pub(crate) fn native_delete_classified(&self, classified_id: UUID) -> Result<(), Error> { + let mut packet = crate::packets::ClassifiedDeletePacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + packet.data.classified_id = classified_id; + self.send_packet( + crate::packets::PacketType::ClassifiedDelete, + packet.to_bytes_with_method()?, + ) + } + + async fn get_map( + &self, + uri: Uri, + cancellation: libremetaverse_types::compat::CancellationToken, + ) -> Result, Error> { + cancellation.throw_if_cancellation_requested()?; + let response = self + .client + .http_caps_client() + .get(uri, cancellation, None) + .await; + let (response, data) = match response { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(None), + }; + if !response.is_success_status_code() { + return Ok(None); + } + match OSDParser::deserialize_with_bytes(data) { + Ok(OSD::Map(map)) => OSDMap::new_with_dictionary(map).map(Some), + _ => Ok(None), + } + } + + async fn get_capability_map( + &self, + capability: &str, + suffix: &str, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + let Some(uri) = self.native_capability(capability, &cancellation)? else { + return Ok(None); + }; + self.get_map(Uri(format!("{}{suffix}", uri.0)), cancellation) + .await + } + + async fn post_capability_map( + &self, + capability: &str, + payload: OSDMap, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + let Some(uri) = self.native_capability(capability, &cancellation)? else { + return Ok(None); + }; + let response = self + .client + .http_caps_client() + .post_with_uri_osd_format_osd_cancellation_token_i_progress( + uri, + libremetaverse_structured_data::OSDFormat::Xml, + OSD::Map(payload.snapshot()), + cancellation, + None, + ) + .await; + let (response, data) = match response { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(None), + }; + if !response.is_success_status_code() { + return Ok(None); + } + if data.is_empty() { + return OSDMap::new_with_dictionary(HashMap::new()).map(Some); + } + match OSDParser::deserialize_with_bytes(data) { + Ok(OSD::Map(map)) => OSDMap::new_with_dictionary(map).map(Some), + _ => Ok(None), + } + } + + async fn get_experience_list( + &self, + capability: &str, + suffix: &str, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map(capability, suffix, cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = ExperienceListMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + Ok(Some(result)) + } + + pub(crate) async fn native_get_agent_experiences( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let result = self + .get_experience_list("AgentExperiences", "", cancellation_token) + .await?; + if let Some(message) = result.clone() { + self.native_set_agent_experience_list(Some(message.clone())); + self.inner + .agent_experiences_updated + .emit(AgentExperiencesEventArgs::new(message)?); + } + Ok(result) + } + + pub(crate) async fn native_get_admin_experiences( + &self, + cancellation_token: Option, + ) -> Result, Error> { + self.get_experience_list("GetAdminExperiences", "", cancellation_token) + .await + } + + pub(crate) async fn native_get_creator_experiences( + &self, + cancellation_token: Option, + ) -> Result, Error> { + self.get_experience_list("GetCreatorExperiences", "", cancellation_token) + .await + } + + pub(crate) async fn native_get_group_experiences( + &self, + group_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + self.get_experience_list( + "GroupExperiences", + &format!("?{group_id}"), + cancellation_token, + ) + .await + } + + async fn get_experience_preferences_for_capability( + &self, + capability: &str, + suffix: &str, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map(capability, suffix, cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = ExperiencePreferencesMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + self.native_set_experience_preferences(Some(result.clone())); + self.inner + .experience_preferences_updated + .emit(ExperiencePreferencesEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_get_agent_experience_permissions( + &self, + cancellation_token: Option, + ) -> Result, Error> { + self.get_experience_preferences_for_capability("GetExperiences", "", cancellation_token) + .await + } + + pub(crate) async fn native_get_experience_preferences( + &self, + cancellation_token: Option, + ) -> Result, Error> { + self.get_experience_preferences_for_capability( + "ExperiencePreferences", + "", + cancellation_token, + ) + .await + } + + pub(crate) async fn native_set_experience_preferences_async( + &self, + preferences: ExperiencePreferencesMessage, + cancellation_token: Option, + ) -> Result<(), Error> { + let payload = preferences.serialize()?; + let _ = self + .post_capability_map("ExperiencePreferences", payload, cancellation_token) + .await?; + Ok(()) + } + + pub(crate) async fn native_get_experience_permission( + &self, + experience_id: UUID, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map( + "ExperiencePreferences", + &format!("?{experience_id}"), + cancellation_token, + ) + .await? + else { + return Ok(None); + }; + Self::extract_experience_permission(map, experience_id).map(Some) + } + + pub(crate) async fn native_set_experience_permission( + &self, + experience_id: UUID, + permission: String, + cancellation_token: Option, + ) -> Result { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let Some(uri) = self.native_capability("ExperiencePreferences", &cancellation)? else { + return Ok(false); + }; + let payload = OSD::Map(HashMap::from([( + experience_id.to_string(), + OSD::Map(HashMap::from([( + "permission".into(), + OSD::String(permission), + )])), + )])); + let response = self + .client + .http_caps_client() + .put_with_uri_osd_format_osd_cancellation_token_i_progress( + uri, + libremetaverse_structured_data::OSDFormat::Xml, + payload, + cancellation, + None, + ) + .await; + match response { + Ok((response, _)) => Ok(response.is_success_status_code()), + Err(Error::Cancelled) => Err(Error::Cancelled), + Err(_) => Ok(false), + } + } + + pub(crate) async fn native_forget_experience_permission( + &self, + experience_id: UUID, + cancellation_token: Option, + ) -> Result { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let Some(uri) = self.native_capability("ExperiencePreferences", &cancellation)? else { + return Ok(false); + }; + let response = self + .client + .http_caps_client() + .delete_with_uri_string_bytes_cancellation_token_i_progress( + Uri(format!("{}?{experience_id}", uri.0)), + String::new(), + Vec::new(), + cancellation, + None, + ) + .await; + match response { + Ok((response, _)) => Ok(response.is_success_status_code()), + Err(Error::Cancelled) => Err(Error::Cancelled), + Err(_) => Ok(false), + } + } + + pub(crate) async fn native_get_experience_info( + &self, + experience_ids: Box>, + cancellation_token: Option, + ) -> Result, Error> { + let ids: Vec<_> = experience_ids.collect(); + if ids.is_empty() { + return Ok(Some(ExperienceInfoMessage::new()?)); + } + let mut suffix = format!("/id/?page_size={}", ids.len()); + for id in ids { + use std::fmt::Write as _; + let _ = write!(suffix, "&public_id={id}"); + } + self.native_get_experience_info_by_uri("GetExperienceInfo", &suffix, cancellation_token) + .await + } + + pub(crate) async fn native_find_experience_by_name( + &self, + query: String, + page: Option, + page_size: Option, + cancellation_token: Option, + ) -> Result, Error> { + let suffix = format!( + "?page={}&page_size={}&query={}", + page.unwrap_or(0), + page_size.unwrap_or(30), + percent_encode(&query) + ); + self.native_get_experience_info_by_uri("FindExperienceByName", &suffix, cancellation_token) + .await + } + + async fn native_get_experience_info_by_uri( + &self, + capability: &str, + suffix: &str, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map(capability, suffix, cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = ExperienceInfoMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + self.inner + .experience_info_received + .emit(ExperienceInfoEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_update_experience( + &self, + info: ExperienceInfo, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .post_capability_map("UpdateExperience", info.serialize()?, cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = ExperienceInfoMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + Ok(result.experiences.into_iter().next()) + } + + pub(crate) async fn native_is_experience_role( + &self, + capability: &str, + experience_id: UUID, + cancellation_token: Option, + ) -> Result { + let Some(map) = self + .get_capability_map( + capability, + &format!("?experience_id={experience_id}"), + cancellation_token, + ) + .await? + else { + return Ok(false); + }; + map.get("status") + .map_or(Ok(false), |value| value.as_boolean()) + .or(Ok(false)) + } + + pub(crate) async fn native_get_region_experiences( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map("RegionExperiences", "", cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = RegionExperiencesMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + self.native_set_last_region_experiences(Some(result.clone())); + self.inner + .region_experiences_updated + .emit(RegionExperiencesEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_chatter_box_accept_invite( + &self, + session_id: UUID, + cancellation_token: Option, + ) -> Result<(), Error> { + let mut message = crate::messages::linden::ChatSessionAcceptInvitation::new()?; + message.session_id = session_id; + if self + .post_capability_map( + "ChatSessionRequest", + message.serialize()?, + cancellation_token, + ) + .await? + .is_none() + { + return Err(Error::InvalidOperation); + } + Ok(()) + } + + pub(crate) async fn native_start_im_conference( + &self, + participants: Vec, + session_id: UUID, + cancellation_token: Option, + ) -> Result<(), Error> { + let mut message = crate::messages::linden::ChatSessionRequestStartConference::new()?; + message.agents_block = participants; + message.session_id = session_id; + if self + .post_capability_map( + "ChatSessionRequest", + message.serialize()?, + cancellation_token, + ) + .await? + .is_none() + { + return Err(Error::InvalidOperation); + } + Ok(()) + } + + pub(crate) fn native_moderate_chat_sessions( + &self, + session_id: UUID, + member_id: UUID, + key: String, + moderate: bool, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let capability_available = self + .client + .network() + .current_sim() + .and_then(|simulator| simulator.native_caps()) + .and_then(|caps| { + caps.capability_uri("ChatSessionRequest".into()) + .ok() + .flatten() + }) + .is_some(); + if !capability_available { + return Err(Error::InvalidOperation); + } + let mut message = crate::messages::linden::ChatSessionRequestMuteUpdate::new()?; + message.agent_id = member_id; + message.request_key = key; + message.request_value = moderate; + message.session_id = session_id; + let payload = message.serialize()?; + let client = Arc::clone(&self.client); + std::thread::Builder::new() + .name("libremetaverse-chat-moderation".into()) + .spawn(move || { + let Ok(runtime) = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + else { + return; + }; + let Ok(manager) = AgentManager::native_new(Some(client)) else { + return; + }; + let _ = runtime.block_on(manager.post_capability_map( + "ChatSessionRequest", + payload, + Some(cancellation), + )); + }) + .map(|_| ()) + .map_err(|_| Error::InvalidOperation) + } + + pub(crate) async fn native_set_display_name( + &self, + old_name: String, + new_name: String, + cancellation_token: Option, + ) -> Result<(), Error> { + let mut message = crate::messages::linden::SetDisplayNameMessage::new()?; + message.old_display_name = old_name; + message.new_display_name = new_name; + let _ = self + .post_capability_map("SetDisplayName", message.serialize()?, cancellation_token) + .await?; + Ok(()) + } + + pub(crate) async fn native_update_agent_language( + &self, + language: String, + is_public: bool, + cancellation_token: Option, + ) -> Result<(), Error> { + let mut message = crate::messages::linden::UpdateAgentLanguageMessage::new()?; + message.language = language; + message.language_public = is_public; + let _ = self + .post_capability_map( + "UpdateAgentLanguage", + message.serialize()?, + cancellation_token, + ) + .await?; + Ok(()) + } + + pub(crate) async fn native_set_agent_access_async( + &self, + access: String, + cancellation_token: Option, + ) -> Result { + let payload = OSDMap::new_with_dictionary(HashMap::from([( + "access_prefs".into(), + OSD::Map(HashMap::from([("max".into(), OSD::String(access))])), + )]))?; + let Some(result) = self + .post_capability_map("UpdateAgentInformation", payload, cancellation_token) + .await? + else { + return AgentAccessEventArgs::new(false, self.native_agent_access()); + }; + let new_level = match result.get("access_prefs") { + Some(OSD::Map(values)) => values + .get("max") + .map_or_else(|| Ok(self.native_agent_access()), OSD::as_string)?, + _ => self.native_agent_access(), + }; + self.native_set_agent_access(new_level.clone()); + AgentAccessEventArgs::new(true, new_level) + } + + pub(crate) async fn native_set_hover_height_async( + &self, + hover_height: f64, + cancellation_token: Option, + ) -> Result<(), Error> { + let payload = OSDMap::new_with_dictionary(HashMap::from([( + "hover_height".into(), + OSD::Real(hover_height), + )]))?; + if let Some(result) = self + .post_capability_map("AgentPreferences", payload, cancellation_token) + .await? + && let Some(value) = result.get("hover_height") + && let Ok(value) = value.as_real() + { + self.native_set_hover_height(value as f32); + } + Ok(()) + } + + pub(crate) async fn native_get_agent_preferences( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map("AgentPreferences", "", cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = AgentPreferencesMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + self.native_set_hover_height(result.hover_height); + *self + .inner + .agent_preferences + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result.clone()); + self.inner + .agent_preferences_updated + .emit(AgentPreferencesEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_get_avatar_render_info( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map("AvatarRenderInfo", "", cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = AvatarRenderInfoMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + self.native_set_avatar_render_info(Some(result.clone())); + self.inner + .avatar_render_info_updated + .emit(AvatarRenderInfoEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_post_avatar_render_info( + &self, + agents: HashMap, + cancellation_token: Option, + ) -> Result<(), Error> { + if agents.is_empty() { + return Ok(()); + } + let message = AvatarRenderInfoMessage { + agents: agents + .into_iter() + .map(|(id, (weight, too_complex))| { + ( + id, + crate::messages::linden::AvatarRenderInfoMessageAvatarInfo { + too_complex, + weight, + }, + ) + }) + .collect(), + over_limit: 0, + reporting_limit: 0, + }; + let _ = self + .post_capability_map("AvatarRenderInfo", message.serialize()?, cancellation_token) + .await?; + Ok(()) + } + + pub(crate) async fn native_post_local_avatar_render_info( + &self, + weight: i32, + too_complex: bool, + cancellation_token: Option, + ) -> Result<(), Error> { + self.native_post_avatar_render_info( + HashMap::from([(self.native_agent_id(), (weight, too_complex))]), + cancellation_token, + ) + .await + } + + pub(crate) async fn native_get_viewer_benefits( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map("ViewerBenefits", "", cancellation_token) + .await? + else { + return Ok(None); + }; + let account_level_benefits = map.get("account_level_benefits").and_then(|value| { + let OSD::Map(values) = value else { + return None; + }; + crate::AccountLevelBenefits::new_with_osd_map( + OSDMap::new_with_dictionary(values.clone()).ok()?, + ) + .ok() + }); + let mut result = ViewerBenefitsMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + if let Some(benefits) = account_level_benefits { + self.native_set_benefits(benefits); + } + self.native_set_viewer_benefits(Some(result.clone())); + self.inner + .viewer_benefits_updated + .emit(ViewerBenefitsEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_send_viewer_stats( + &self, + stats: ViewerStatsMessage, + cancellation_token: Option, + ) -> Result<(), Error> { + let _ = self + .post_capability_map("ViewerStats", stats.serialize()?, cancellation_token) + .await?; + Ok(()) + } + + pub(crate) async fn native_request_nav_mesh_generation_status( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let Some(map) = self + .get_capability_map("NavMeshGenerationStatus", "", cancellation_token) + .await? + else { + return Ok(None); + }; + let mut result = crate::messages::linden::NavMeshStatusUpdateMessage::new()?; + if result.deserialize(map).is_err() { + return Ok(None); + } + self.native_set_last_nav_mesh_status(Some(result.clone())); + Ok(Some(result)) + } + + pub(crate) async fn native_request_nav_mesh_rebake( + &self, + cancellation_token: Option, + ) -> Result<(), Error> { + let empty = OSDMap::new_with_dictionary(HashMap::new())?; + let _ = self + .post_capability_map("NavMeshGenerationStatus", empty, cancellation_token) + .await?; + Ok(()) + } + + pub(crate) async fn native_get_product_info( + &self, + cancellation_token: Option, + ) -> Result, Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + let Some(uri) = self.native_capability("ProductInfoRequest", &cancellation)? else { + return Ok(None); + }; + let response = self + .client + .http_caps_client() + .get(uri, cancellation, None) + .await; + let (response, data) = match response { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(None), + }; + if !response.is_success_status_code() { + return Ok(None); + } + let map = match OSDParser::deserialize_with_bytes(data) { + Ok(OSD::Map(map)) => map, + Ok(OSD::Array(products)) => HashMap::from([("products".into(), OSD::Array(products))]), + _ => return Ok(None), + }; + let mut result = ProductInfoRequestMessage::new()?; + if result + .deserialize(OSDMap::new_with_dictionary(map)?) + .is_err() + { + return Ok(None); + } + self.native_set_product_info(Some(result.clone())); + self.inner + .product_info_updated + .emit(ProductInfoEventArgs::new(result.clone())?); + Ok(Some(result)) + } + + pub(crate) async fn native_get_attachment_resources( + &self, + cancellation_token: Option, + ) -> Result< + ( + bool, + Option, + ), + Error, + > { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let Some(capability) = self.native_capability("AttachmentResources", &cancellation)? else { + return Ok((false, None)); + }; + let (response, data) = self + .client + .http_caps_client() + .get(capability, cancellation, None) + .await?; + if !response.is_success_status_code() { + return Ok((false, None)); + } + let Ok(parsed) = OSDParser::deserialize_with_bytes(data) else { + return Ok((false, None)); + }; + match crate::messages::linden::AttachmentResourcesMessage::from_osd(parsed) { + Ok(info) => Ok((true, Some(info))), + Err(_) => Ok((false, None)), + } + } + + pub(crate) async fn native_retrieve_instant_messages( + &self, + cancellation_token: Option, + ) -> Result<(), Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + let Some(offline) = self.native_capability("ReadOfflineMsgs", &cancellation)? else { + return self.native_retrieve_instant_messages_legacy(); + }; + if self + .native_capability("AcceptFriendship", &cancellation)? + .is_none() + || self + .native_capability("AcceptGroupInvite", &cancellation)? + .is_none() + { + return self.native_retrieve_instant_messages_legacy(); + } + let (_, data) = self + .client + .http_caps_client() + .get(offline, cancellation, None) + .await?; + self.deliver_offline_messages(&data) + } + + fn native_capability( + &self, + name: &str, + cancellation: &libremetaverse_types::compat::CancellationToken, + ) -> Result, Error> { + let Some(caps) = self + .client + .network() + .current_sim() + .and_then(|simulator| simulator.native_caps()) + else { + return Ok(None); + }; + let deadline = Instant::now() + Duration::from_secs(2); + loop { + if let Some(uri) = caps.capability_uri(name.to_owned())? { + return Ok(Some(uri)); + } + if caps.seed_request_finished() || Instant::now() >= deadline { + return Ok(None); + } + cancellation.throw_if_cancellation_requested()?; + std::thread::sleep(Duration::from_millis(1)); + } + } + + pub(crate) async fn native_query_experiences_on_parcel( + &self, + parcel_local_id: i32, + experience_ids: Box>, + cancellation_token: Option, + ) -> Result>, Error> { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let Some(capability) = self.native_capability("ExperienceQuery", &cancellation)? else { + return Ok(None); + }; + let ids: Vec<_> = experience_ids.collect(); + let mut uri = format!("{}?parcelid={parcel_local_id}", capability.0); + if !ids.is_empty() { + uri.push_str("&experiences="); + uri.push_str( + &ids.iter() + .map(ToString::to_string) + .collect::>() + .join(","), + ); + } + let response = self + .client + .http_caps_client() + .get(Uri(uri), cancellation.clone(), None) + .await; + let (response, data) = match response { + Ok(result) => result, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(None), + }; + if !response.is_success_status_code() { + return Ok(None); + } + let OSD::Map(mut map) = OSDParser::deserialize_with_bytes(data)? else { + return Ok(None); + }; + let Some(OSD::Map(experiences)) = map.remove("experiences") else { + return Ok(None); + }; + let mut result = HashMap::with_capacity(experiences.len()); + for (id, allowed) in experiences { + let id = UUID::new_with_string(id)?; + result.insert(id, allowed.as_boolean()?); + } + Ok(Some(result)) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) async fn native_send_postcard( + &self, + jpeg_image_data: Vec, + to_email: String, + from_name: String, + subject: String, + message: String, + global_position: Vector3, + cancellation_token: Option, + ) -> Result { + let cancellation = cancellation_token.unwrap_or_else(|| self.client.cancellation_token()); + cancellation.throw_if_cancellation_requested()?; + let Some(capability) = self.native_capability("SendPostcard", &cancellation)? else { + return Ok(false); + }; + let mut postcard = crate::messages::linden::SendPostcardMessage::new()?; + postcard.from_name = from_name; + postcard.to_email = to_email; + postcard.subject = subject; + postcard.message = message; + postcard.global_position = global_position; + let payload = OSD::Map(postcard.serialize()?.snapshot()); + let first = self + .client + .http_caps_client() + .post_with_uri_osd_format_osd_cancellation_token_i_progress( + capability, + libremetaverse_structured_data::OSDFormat::Xml, + payload, + cancellation.clone(), + None, + ) + .await; + let (response, data) = match first { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(false), + }; + if !response.is_success_status_code() { + return Ok(false); + } + let Ok(OSD::Map(mut response)) = OSDParser::deserialize_with_bytes(data) else { + return Ok(false); + }; + let Some(uploader) = response.remove("uploader") else { + return Ok(false); + }; + let uploader = uploader.as_string()?; + if uploader.is_empty() { + return Ok(false); + } + let upload = self + .client + .http_caps_client() + .post_with_uri_string_bytes_cancellation_token_i_progress( + Uri(uploader), + "application/octet-stream".into(), + jpeg_image_data, + cancellation, + None, + ) + .await; + let (response, data) = match upload { + Ok(value) => value, + Err(Error::Cancelled) => return Err(Error::Cancelled), + Err(_) => return Ok(false), + }; + if !response.is_success_status_code() { + return Ok(false); + } + let Ok(OSD::Map(mut response)) = OSDParser::deserialize_with_bytes(data) else { + return Ok(false); + }; + Ok(response + .remove("state") + .is_some_and(|state| state.as_string().as_deref() == Ok("complete"))) + } + + fn native_retrieve_instant_messages_legacy(&self) -> Result<(), Error> { + let mut packet = crate::packets::RetrieveInstantMessagesPacket::new_with_constructor()?; + packet.agent_data.agent_id = self.native_agent_id(); + packet.agent_data.session_id = self.native_session_id(); + let data = packet.to_bytes_with_method()?; + let length = i32::try_from(data.len()).map_err(|_| Error::Argument)?; + let simulator = self + .client + .network() + .current_sim() + .ok_or(Error::InvalidOperation)?; + simulator.native_send_packet_data( + data, + length, + crate::packets::PacketType::RetrieveInstantMessages, + false, + ) + } + + fn deliver_offline_messages(&self, data: &[u8]) -> Result<(), Error> { + let root = OSDParser::deserialize_with_bytes(data.to_vec())?; + let messages = match root { + OSD::Array(values) => values.into_iter().next().and_then(|value| match value { + OSD::Array(messages) => Some(messages), + _ => None, + }), + OSD::Map(mut map) => map.remove("messages").and_then(|value| match value { + OSD::Array(messages) => Some(messages), + _ => None, + }), + _ => None, + } + .ok_or(Error::Argument)?; + + for value in messages { + let OSD::Map(map) = value else { + return Err(Error::Argument); + }; + let message = offline_message(&map)?; + if message.group_im { + self.group_chat_sessions + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .entry(message.im_session_id) + .or_default(); + } + self.inner.instant_message.emit(InstantMessageEventArgs { + im: message, + simulator: None, + }); + } + Ok(()) + } + + pub(crate) fn extract_experience_permission( + map: OSDMap, + experience_id: UUID, + ) -> Result { + if osd_uuid_array_contains(&map, "experiences", experience_id)? { + return Ok("Allow".into()); + } + if osd_uuid_array_contains(&map, "blocked", experience_id)? { + return Ok("Block".into()); + } + Ok("Forget".into()) + } +} + +struct UdpAssetAbortGuard { + simulator: Simulator, + transfer_id: UUID, + completed: bool, +} + +impl UdpAssetAbortGuard { + fn new(simulator: Simulator, transfer_id: UUID) -> Self { + Self { + simulator, + transfer_id, + completed: false, + } + } + + fn complete(&mut self) { + self.completed = true; + } +} + +impl Drop for UdpAssetAbortGuard { + fn drop(&mut self) { + if self.completed { + return; + } + let Ok(mut packet) = crate::packets::TransferAbortPacket::new_with_constructor() else { + return; + }; + packet.transfer_info.channel_type = crate::ChannelType::Asset as i32; + packet.transfer_info.transfer_id = self.transfer_id; + let Ok(data) = packet.to_bytes_with_method() else { + return; + }; + let Ok(length) = i32::try_from(data.len()) else { + return; + }; + let do_zerocode = data + .first() + .is_some_and(|flags| flags & crate::Helpers::MSG_ZEROCODED != 0); + let _ = self.simulator.native_send_packet_data( + data, + length, + crate::packets::PacketType::TransferAbort, + do_zerocode, + ); + } +} + +fn required<'a>(map: &'a HashMap, key: &str) -> Result<&'a OSD, Error> { + map.get(key).ok_or(Error::Argument) +} + +fn gesture_chat_channel(text: &str) -> (i32, String) { + let Some(rest) = text.strip_prefix('/') else { + return (0, text.to_owned()); + }; + let bytes = rest.as_bytes(); + let mut end = usize::from(bytes.first() == Some(&b'-')); + let digit_start = end; + while bytes.get(end).is_some_and(u8::is_ascii_digit) { + end += 1; + } + if end == digit_start { + return (0, text.to_owned()); + } + let Ok(channel) = rest[..end].parse::() else { + return (0, text.to_owned()); + }; + let message = rest[end..] + .trim_start_matches(char::is_whitespace) + .to_owned(); + (channel, message) +} + +fn decode_raw_packet(data: &[u8]) -> Result { + let mut packet = T::new_generated(); + let mut position = 0_i32; + let mut packet_end = i32::try_from(data.len()).map_err(|_| Error::Argument)? - 1; + let mut zero_buffer = + vec![0_u8; crate::udp_transport::UdpTransportConfig::default().max_decoded_packet_size]; + packet.decode_from_bytes( + data, + &mut position, + &mut packet_end, + Some(zero_buffer.as_mut_slice()), + )?; + Ok(packet) +} + +fn wire_string(mut bytes: Vec) -> String { + while bytes.last() == Some(&0) { + bytes.pop(); + } + String::from_utf8_lossy(&bytes).into_owned() +} + +fn chat_audible_level(value: u8) -> Result { + match value { + 0 => Ok(crate::ChatAudibleLevel::Barely), + 1 => Ok(crate::ChatAudibleLevel::Fully), + u8::MAX => Ok(crate::ChatAudibleLevel::Not), + _ => Err(Error::Argument), + } +} + +fn chat_source_type(value: u8) -> Result { + match value { + 0 => Ok(crate::ChatSourceType::System), + 1 => Ok(crate::ChatSourceType::Agent), + 2 => Ok(crate::ChatSourceType::Object), + _ => Err(Error::Argument), + } +} + +fn chat_type(value: u8) -> Result { + match value { + 0 => Ok(crate::ChatType::Whisper), + 1 => Ok(crate::ChatType::Normal), + 2 => Ok(crate::ChatType::Shout), + 4 => Ok(crate::ChatType::StartTyping), + 5 => Ok(crate::ChatType::StopTyping), + 6 => Ok(crate::ChatType::Debug), + 8 => Ok(crate::ChatType::OwnerSay), + 9 => Ok(crate::ChatType::RegionSayTo), + u8::MAX => Ok(crate::ChatType::RegionSay), + _ => Err(Error::Argument), + } +} + +fn instant_message_online(value: u8) -> Result { + match value { + 0 => Ok(InstantMessageOnline::Online), + 1 => Ok(InstantMessageOnline::Offline), + _ => Err(Error::Argument), + } +} + +fn instant_message_dialog(value: u8) -> Result { + use InstantMessageDialog as Dialog; + match value { + 0 => Ok(Dialog::MessageFromAgent), + 1 => Ok(Dialog::MessageBox), + 3 => Ok(Dialog::GroupInvitation), + 4 => Ok(Dialog::InventoryOffered), + 5 => Ok(Dialog::InventoryAccepted), + 6 => Ok(Dialog::InventoryDeclined), + 7 => Ok(Dialog::GroupVote), + 9 => Ok(Dialog::TaskInventoryOffered), + 10 => Ok(Dialog::TaskInventoryAccepted), + 11 => Ok(Dialog::TaskInventoryDeclined), + 12 => Ok(Dialog::NewUserDefault), + 13 => Ok(Dialog::SessionAdd), + 14 => Ok(Dialog::SessionOfflineAdd), + 15 => Ok(Dialog::SessionGroupStart), + 16 => Ok(Dialog::SessionCardlessStart), + 17 => Ok(Dialog::SessionSend), + 18 => Ok(Dialog::SessionDrop), + 19 => Ok(Dialog::MessageFromObject), + 20 => Ok(Dialog::BusyAutoResponse), + 21 => Ok(Dialog::ConsoleAndChatHistory), + 22 => Ok(Dialog::RequestTeleport), + 23 => Ok(Dialog::AcceptTeleport), + 24 => Ok(Dialog::DenyTeleport), + 25 => Ok(Dialog::GodLikeRequestTeleport), + 26 => Ok(Dialog::RequestLure), + 28 => Ok(Dialog::GotoUrl), + 29 => Ok(Dialog::Session911Start), + 30 => Ok(Dialog::Lure911), + 31 => Ok(Dialog::FromTaskAsAlert), + 32 => Ok(Dialog::GroupNotice), + 33 => Ok(Dialog::GroupNoticeInventoryAccepted), + 34 => Ok(Dialog::GroupNoticeInventoryDeclined), + 35 => Ok(Dialog::GroupInvitationAccept), + 36 => Ok(Dialog::GroupInvitationDecline), + 37 => Ok(Dialog::GroupNoticeRequested), + 38 => Ok(Dialog::FriendshipOffered), + 39 => Ok(Dialog::FriendshipAccepted), + 40 => Ok(Dialog::FriendshipDeclined), + 41 => Ok(Dialog::StartTyping), + 42 => Ok(Dialog::StopTyping), + _ => Err(Error::Argument), + } +} + +fn split_utf8(message: &str, max_bytes: usize, max_parts: usize) -> Vec { + assert!(max_bytes > 0, "split size must be at least one byte"); + if message.is_empty() { + return vec![String::new()]; + } + let mut parts = Vec::new(); + let mut start = 0; + while start < message.len() && parts.len() < max_parts { + let proposed = start.saturating_add(max_bytes).min(message.len()); + let mut end = proposed; + while end > start && !message.is_char_boundary(end) { + end -= 1; + } + let end = if end == start { + message[start..] + .char_indices() + .nth(1) + .map_or(message.len(), |(offset, _)| start + offset) + } else { + end + }; + parts.push(message[start..end].to_owned()); + start = end; + } + parts +} + +fn percent_encode(value: &str) -> String { + let mut encoded = String::with_capacity(value.len()); + for byte in value.bytes() { + if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') { + encoded.push(char::from(byte)); + } else { + use std::fmt::Write as _; + let _ = write!(encoded, "%{byte:02X}"); + } + } + encoded +} + +fn optional_uuid(map: &HashMap, key: &str) -> Result { + map.get(key).map_or(Ok(UUID::zero()), OSD::as_uuid) +} + +fn optional_i32(map: &HashMap, key: &str, fallback: i32) -> Result { + map.get(key).map_or(Ok(fallback), OSD::as_integer) +} + +fn optional_u32(map: &HashMap, key: &str, fallback: u32) -> Result { + map.get(key).map_or(Ok(fallback), OSD::as_u_integer) +} + +fn offline_message(map: &HashMap) -> Result { + let dialog = match required(map, "dialog")?.as_integer()? { + 0 => InstantMessageDialog::MessageFromAgent, + 17 => InstantMessageDialog::SessionSend, + _ => return Err(Error::Argument), + }; + let offline = match optional_i32(map, "offline", 1)? { + 0 => InstantMessageOnline::Online, + 1 => InstantMessageOnline::Offline, + _ => return Err(Error::Argument), + }; + let position = if let Some(value) = map.get("position") { + value.as_vector3()? + } else { + Vector3 { + #[allow(clippy::cast_possible_truncation)] + x: required(map, "local_x")?.as_real()? as f32, + #[allow(clippy::cast_possible_truncation)] + y: required(map, "local_y")?.as_real()? as f32, + #[allow(clippy::cast_possible_truncation)] + z: required(map, "local_z")?.as_real()? as f32, + } + }; + let ticks = optional_i32(map, "timestamp", 0)?.max(0); + Ok(InstantMessage { + binary_bucket: map + .get("binary_bucket") + .map_or_else(|| Ok(vec![0]), OSD::as_binary)?, + dialog, + from_agent_id: required(map, "from_agent_id")?.as_uuid()?, + from_agent_name: required(map, "from_agent_name")?.as_string()?, + group_im: map + .get("from_group") + .is_some_and(|value| value.as_boolean() == Ok(true)), + im_session_id: required(map, "transaction-id")?.as_uuid()?, + message: required(map, "message")?.as_string()?, + offline, + parent_estate_id: optional_u32(map, "parent_estate_id", 1)?, + position, + region_id: optional_uuid(map, "region_id")?, + timestamp: UNIX_EPOCH + Duration::from_secs(u64::try_from(ticks).unwrap_or_default()), + to_agent_id: required(map, "to_agent_id")?.as_uuid()?, + }) +} + +fn osd_uuid_array_contains(map: &OSDMap, key: &str, id: UUID) -> Result { + let Some(OSD::Array(values)) = map.get(key) else { + return Ok(false); + }; + for value in values { + if value.as_uuid()? == id { + return Ok(true); + } + } + Ok(false) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + + fn loopback(port: u16) -> SocketAddr { + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port) + } + + fn simulator(client: &GridClient, endpoint: SocketAddr) -> Simulator { + Simulator::native_new(client.clone(), endpoint, 0x1000, Some(256), Some(256)) + .expect("simulator") + } + + fn transport_wire(data: Vec) -> Vec { + if data + .first() + .is_none_or(|flags| flags & crate::Helpers::MSG_ZEROCODED == 0) + { + return data; + } + let mut encoded = vec![0_u8; data.len().saturating_mul(2).saturating_add(2)]; + let length = crate::packet_wire::zero_encode( + Some(&data), + i32::try_from(data.len()).unwrap(), + Some(&mut encoded), + ) + .unwrap(); + encoded.truncate(usize::try_from(length).unwrap()); + encoded + } + + #[test] + fn chat_uses_utf8_safe_protocol_chunks_and_exact_identity() { + let client = Arc::new(GridClient::new().expect("client")); + let manager = AgentManager::native_new(Some(client)).unwrap(); + manager.native_set_agent_id( + UUID::new_with_string("11111111-1111-1111-1111-111111111111".into()).unwrap(), + ); + manager.native_set_session_id( + UUID::new_with_string("22222222-2222-2222-2222-222222222222".into()).unwrap(), + ); + let message = "é".repeat(600); + let packets = manager + .native_chat_payloads(message.clone(), 7, ChatType::Normal, Some(true)) + .unwrap(); + + let mut reconstructed = String::new(); + assert_eq!(packets.len(), 2); + for (_, data) in packets { + let data = transport_wire(data); + let packet = decode_raw_packet::(&data).unwrap(); + assert_eq!(packet.agent_data.agent_id, manager.native_agent_id()); + assert_eq!(packet.agent_data.session_id, manager.native_session_id()); + assert_eq!(packet.chat_data.channel, 7); + assert_eq!(packet.chat_data.type_, ChatType::Normal as u8); + assert!(packet.chat_data.message.len() <= MAX_CHAT_BYTES); + reconstructed.push_str(&wire_string(packet.chat_data.message)); + } + assert_eq!(reconstructed, message); + } + + #[test] + fn gesture_chat_channel_matches_reference_regex_behavior() { + assert_eq!(gesture_chat_channel("hello"), (0, "hello".into())); + assert_eq!(gesture_chat_channel("/5 hello"), (5, "hello".into())); + assert_eq!(gesture_chat_channel("/-42answer"), (-42, "answer".into())); + assert_eq!(gesture_chat_channel("/x nope"), (0, "/x nope".into())); + assert_eq!( + gesture_chat_channel("/999999999999999999 text"), + (0, "/999999999999999999 text".into()) + ); + } + + #[test] + fn instant_message_debug_redacts_message_and_binary_payload() { + let message = InstantMessage { + binary_bucket: b"private-bucket".to_vec(), + dialog: InstantMessageDialog::MessageFromAgent, + from_agent_id: UUID::random().unwrap(), + from_agent_name: "Sender Resident".into(), + group_im: false, + im_session_id: UUID::random().unwrap(), + message: "private-message".into(), + offline: InstantMessageOnline::Online, + parent_estate_id: 1, + position: Vector3::zero(), + region_id: UUID::random().unwrap(), + timestamp: UNIX_EPOCH, + to_agent_id: UUID::random().unwrap(), + }; + let diagnostic = format!("{message:?}"); + assert!(!diagnostic.contains("private-message")); + assert!(!diagnostic.contains("private-bucket")); + assert!(!diagnostic.contains("Sender Resident")); + assert!(diagnostic.contains("message_len")); + } + + #[test] + fn raw_agent_packets_update_state_and_raise_callbacks_outside_locks() { + let client = GridClient::new().unwrap(); + let sim = simulator(&client, loopback(13001)); + let inner = AgentManagerInner::default(); + + let chats = Arc::new(Mutex::new(Vec::new())); + let chat_values = Arc::clone(&chats); + let _chat_subscription = inner.chat_from_simulator.subscribe(Arc::new(move |event| { + chat_values.lock().unwrap().push(event.message()); + })); + let mut chat = crate::packets::ChatFromSimulatorPacket::new_with_constructor().unwrap(); + chat.chat_data.message = b"hello\0".to_vec(); + chat.chat_data.from_name = b"Grid Resident\0".to_vec(); + chat.chat_data.audible = 1; + chat.chat_data.chat_type = ChatType::Normal as u8; + chat.chat_data.source_type = crate::ChatSourceType::Agent as u8; + inner.handle_raw_packet(crate::network_manager::RawPacketReceivedEventArgs { + packet_type: crate::packets::PacketType::ChatFromSimulator, + data: transport_wire(chat.to_bytes_with_method().unwrap()), + simulator: sim.clone(), + }); + assert_eq!(*chats.lock().unwrap(), ["hello"]); + + let ims = Arc::new(Mutex::new(Vec::new())); + let im_values = Arc::clone(&ims); + let _im_subscription = inner.instant_message.subscribe(Arc::new(move |event| { + im_values.lock().unwrap().push(event.im().message); + })); + let mut im = crate::packets::ImprovedInstantMessagePacket::new_with_constructor().unwrap(); + im.message_block.dialog = InstantMessageDialog::MessageFromAgent as u8; + im.message_block.offline = InstantMessageOnline::Online as u8; + im.message_block.message = b"private\0".to_vec(); + inner + .handle_instant_message_packet(&transport_wire(im.to_bytes_with_method().unwrap()), sim) + .unwrap(); + assert_eq!(*ims.lock().unwrap(), ["private"]); + + let balances = Arc::new(Mutex::new(Vec::new())); + let balance_values = Arc::clone(&balances); + let _balance_subscription = inner.money_balance.subscribe(Arc::new(move |event| { + balance_values.lock().unwrap().push(event.balance()); + })); + let mut money = crate::packets::MoneyBalanceReplyPacket::new_with_constructor().unwrap(); + money.money_data.money_balance = 1234; + money.money_data.description = b"payment\0".to_vec(); + money.transaction_info.item_description = b"item\0".to_vec(); + inner.handle_raw_packet(crate::network_manager::RawPacketReceivedEventArgs { + packet_type: crate::packets::PacketType::MoneyBalanceReply, + data: transport_wire(money.to_bytes_with_method().unwrap()), + simulator: simulator(&client, loopback(13002)), + }); + assert_eq!(*balances.lock().unwrap(), [1234]); + assert_eq!(*inner.balance.read().unwrap(), 1234); + + let identity = Arc::new(Mutex::new(Vec::new())); + let identity_values = Arc::clone(&identity); + let _identity_subscription = inner.agent_data_reply.subscribe(Arc::new(move |event| { + identity_values.lock().unwrap().push(( + event.first_name(), + event.last_name(), + event.active_group_id(), + event.group_title(), + event.group_powers(), + event.group_name(), + )); + })); + let mut identity_packet = + crate::packets::AgentDataUpdatePacket::new_with_constructor().unwrap(); + identity_packet.agent_data.agent_id = UUID::random().unwrap(); + identity_packet.agent_data.first_name = b"Test\0".to_vec(); + identity_packet.agent_data.last_name = b"Resident\0".to_vec(); + identity_packet.agent_data.active_group_id = UUID::random().unwrap(); + identity_packet.agent_data.group_title = b"Builder\0".to_vec(); + identity_packet.agent_data.group_name = b"Builders\0".to_vec(); + identity_packet.agent_data.group_powers = 0x1234; + inner.handle_raw_packet(crate::network_manager::RawPacketReceivedEventArgs { + packet_type: crate::packets::PacketType::AgentDataUpdate, + data: transport_wire(identity_packet.to_bytes_with_method().unwrap()), + simulator: simulator(&client, loopback(13005)), + }); + assert_eq!( + *identity.lock().unwrap(), + [( + "Test".into(), + "Resident".into(), + identity_packet.agent_data.active_group_id, + "Builder".into(), + crate::GroupPowers(0x1234), + "Builders".into(), + )] + ); + assert_eq!(*inner.first_name.read().unwrap(), "Test"); + assert_eq!(*inner.last_name.read().unwrap(), "Resident"); + + let animation_id = UUID::random().unwrap(); + let changed = Arc::new(Mutex::new(Vec::new())); + let changed_values = Arc::clone(&changed); + let _animation_subscription = inner.animations_changed.subscribe(Arc::new(move |event| { + changed_values.lock().unwrap().push(event.animations()); + })); + let mut animations = crate::packets::AvatarAnimationPacket::new_with_constructor().unwrap(); + animations.sender.id = UUID::random().unwrap(); + let mut animation = + crate::packets::AvatarAnimationPacketAnimationListBlock::new_with_constructor() + .unwrap(); + animation.anim_id = animation_id; + animation.anim_sequence_id = 17; + animations.animation_list.push(animation); + inner.handle_raw_packet(crate::network_manager::RawPacketReceivedEventArgs { + packet_type: crate::packets::PacketType::AvatarAnimation, + data: transport_wire(animations.to_bytes_with_method().unwrap()), + simulator: simulator(&client, loopback(13006)), + }); + assert_eq!( + *changed.lock().unwrap(), + [HashMap::from([(animation_id, 17)])] + ); + assert_eq!( + *inner.signaled_animations.read().unwrap(), + HashMap::from([(animation_id, 17)]) + ); + } + + #[test] + fn group_chat_caps_events_track_members_and_raise_compatible_events() { + let client = Arc::new(GridClient::new().unwrap()); + let manager = Arc::new(AgentManager::native_new(Some(Arc::clone(&client))).unwrap()); + let sim = simulator(&client, loopback(13003)); + let session_id = + UUID::new_with_string("33333333-3333-3333-3333-333333333333".into()).unwrap(); + let agent_id = + UUID::new_with_string("44444444-4444-4444-4444-444444444444".into()).unwrap(); + + let joined = Arc::new(Mutex::new(Vec::new())); + let joined_values = Arc::clone(&joined); + let _joined = manager.native_subscribe_group_chat_joined(Arc::new(move |event| { + joined_values.lock().unwrap().push(( + event.session_id(), + event.session_name(), + event.tmp_session_id(), + event.success(), + )); + })); + let manager_for_added = Arc::clone(&manager); + let added = Arc::new(Mutex::new(Vec::new())); + let added_values = Arc::clone(&added); + let _added = manager.native_subscribe_chat_session_member_added(Arc::new(move |event| { + // Taking the collection read lock proves callbacks are not invoked + // while the membership write lock is held. + assert!( + manager_for_added + .group_chat_sessions + .read() + .unwrap() + .contains_key(&event.session_id()) + ); + added_values.lock().unwrap().push(event.agent_id()); + })); + let left = Arc::new(Mutex::new(Vec::new())); + let left_values = Arc::clone(&left); + let _left = manager.native_subscribe_chat_session_member_left(Arc::new(move |event| { + left_values.lock().unwrap().push(event.agent_id()); + })); + + let mut started = + crate::messages::linden::ChatterBoxSessionStartReplyMessage::new().unwrap(); + started.session_id = session_id; + started.session_name = "Group Chat".into(); + started.temp_session_id = UUID::random().unwrap(); + started.success = true; + let mut started_roundtrip = + crate::messages::linden::ChatterBoxSessionStartReplyMessage::new().unwrap(); + started_roundtrip + .deserialize(started.serialize().unwrap()) + .unwrap(); + assert_eq!(started_roundtrip.session_id, session_id); + client + .network() + .dispatch_caps_event("ChatterBoxSessionStartReply", &started, sim.clone()); + assert_eq!( + *joined.lock().unwrap(), + [( + session_id, + "Group Chat".into(), + started.temp_session_id, + true + )] + ); + assert!( + manager + .group_chat_sessions + .read() + .unwrap() + .contains_key(&session_id) + ); + + let mut entered = + crate::messages::linden::ChatterBoxSessionAgentListUpdatesMessage::new().unwrap(); + entered.session_id = session_id; + let mut member = crate::messages::linden:: + ChatterBoxSessionAgentListUpdatesMessageAgentUpdatesBlock::new() + .unwrap(); + member.agent_id = agent_id; + member.transition = "ENTER".into(); + member.can_voice_chat = true; + member.is_moderator = true; + member.mute_text = true; + entered.updates.push(member); + client.network().dispatch_caps_event( + "ChatterBoxSessionAgentListUpdates", + &entered, + sim.clone(), + ); + assert_eq!(*added.lock().unwrap(), [agent_id]); + assert_eq!( + manager.group_chat_sessions.read().unwrap()[&session_id], + [ChatSessionMember { + avatar_key: agent_id, + can_voice_chat: true, + is_moderator: true, + mute_text: true, + mute_voice: false, + }] + ); + + let mut departed = + crate::messages::linden::ChatterBoxSessionAgentListUpdatesMessage::new().unwrap(); + departed.session_id = session_id; + let mut member = crate::messages::linden:: + ChatterBoxSessionAgentListUpdatesMessageAgentUpdatesBlock::new() + .unwrap(); + member.agent_id = agent_id; + member.transition = "LEAVE".into(); + departed.updates.push(member); + client + .network() + .dispatch_caps_event("ChatterBoxSessionAgentListUpdates", &departed, sim); + assert_eq!(*left.lock().unwrap(), [agent_id]); + assert!(manager.group_chat_sessions.read().unwrap()[&session_id].is_empty()); + } + + #[test] + fn caps_invitation_and_display_name_reply_preserve_event_payloads() { + let client = Arc::new(GridClient::new().unwrap()); + let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + let sim = simulator(&client, loopback(13004)); + + let (sender, receiver) = std::sync::mpsc::channel(); + let _im = manager.native_subscribe_im(Arc::new(move |event| { + let _ = sender.send(event.im()); + })); + let mut invitation = crate::messages::linden::ChatterBoxInvitationMessage::new().unwrap(); + invitation.from_agent_id = UUID::random().unwrap(); + invitation.from_agent_name = "Inviter Resident".into(); + invitation.to_agent_id = UUID::random().unwrap(); + invitation.im_session_id = UUID::random().unwrap(); + invitation.message = "conference invitation".into(); + invitation.dialog = InstantMessageDialog::SessionSend; + invitation.offline = InstantMessageOnline::Online; + invitation.timestamp = UNIX_EPOCH + Duration::from_secs(42); + let mut invitation_roundtrip = + crate::messages::linden::ChatterBoxInvitationMessage::new().unwrap(); + invitation_roundtrip + .deserialize(invitation.serialize().unwrap()) + .unwrap(); + assert_eq!(invitation_roundtrip.im_session_id, invitation.im_session_id); + client + .network() + .dispatch_caps_event("ChatterBoxInvitation", &invitation, sim.clone()); + let delivered = receiver.recv_timeout(Duration::from_secs(2)).unwrap(); + assert_eq!(delivered.from_agent_id, invitation.from_agent_id); + assert_eq!(delivered.im_session_id, invitation.im_session_id); + assert_eq!(delivered.message, invitation.message); + assert!( + manager + .group_chat_sessions + .read() + .unwrap() + .contains_key(&invitation.im_session_id) + ); + + let display_events = Arc::new(Mutex::new(Vec::new())); + let display_values = Arc::clone(&display_events); + let _display = manager.native_subscribe_set_display_name_reply(Arc::new(move |event| { + display_values.lock().unwrap().push(( + event.status(), + event.reason(), + event.display_name().display_name(), + )); + })); + let mut reply = crate::messages::linden::SetDisplayNameReplyMessage::new().unwrap(); + reply.status = 200; + reply.reason = "ok".into(); + reply.display_name.set_id(invitation.to_agent_id); + reply + .display_name + .set_display_name(Some("New Display Name".into())); + client + .network() + .dispatch_caps_event("SetDisplayNameReply", &reply, sim); + assert_eq!( + *display_events.lock().unwrap(), + [(200, "ok".into(), Some("New Display Name".into()))] + ); + } + + #[test] + fn profile_capability_payload_matches_reference_keys() { + let profile = crate::AvatarAvatarProperties { + about_text: "about".into(), + born_on: String::new(), + charter_member: String::new(), + first_life_image: UUID::new_with_string("11111111-1111-1111-1111-111111111111".into()) + .unwrap(), + first_life_text: "first".into(), + flags: crate::ProfileFlags( + crate::ProfileFlags::ALLOW_PUBLISH.0 | crate::ProfileFlags::MATURE_PUBLISH.0, + ), + partner: UUID::zero(), + profile_image: UUID::new_with_string("22222222-2222-2222-2222-222222222222".into()) + .unwrap(), + profile_url: "https://example.invalid/profile".into(), + }; + let payload = AgentManager::profile_payload(&profile).unwrap(); + assert_eq!( + payload.get("sl_about_text").unwrap().as_string().unwrap(), + "about" + ); + assert_eq!( + payload.get("fl_about_text").unwrap().as_string().unwrap(), + "first" + ); + assert!(payload.get("allow_publish").unwrap().as_boolean().unwrap()); + assert_eq!( + payload.get("sl_image_id").unwrap().as_uuid().unwrap(), + profile.profile_image + ); + assert_eq!( + payload.get("fl_image_id").unwrap().as_uuid().unwrap(), + profile.first_life_image + ); + } + + #[test] + fn mute_list_parser_preserves_types_names_and_flags() { + let resident = + UUID::new_with_string("11111111-1111-1111-1111-111111111111".into()).unwrap(); + let group = UUID::new_with_string("22222222-2222-2222-2222-222222222222".into()).unwrap(); + let parsed = parse_mute_list(&format!( + "1 {resident} Example Resident|3\n3 {group} Example Group|8\ninvalid line\n" + )); + assert_eq!(parsed.len(), 2); + assert_eq!( + parsed[&format!("{resident}|Example Resident")].type_, + crate::MuteType::Resident + ); + assert_eq!( + parsed[&format!("{resident}|Example Resident")].flags, + crate::MuteFlags(3) + ); + assert_eq!( + parsed[&format!("{group}|Example Group")].type_, + crate::MuteType::Group + ); + assert_eq!( + parsed[&format!("{group}|Example Group")].flags, + crate::MuteFlags(8) + ); + } + + #[test] + fn nav_mesh_caps_event_updates_last_status_before_callback() { + let client = Arc::new(GridClient::new().unwrap()); + let manager = Arc::new(AgentManager::native_new(Some(Arc::clone(&client))).unwrap()); + let sim = simulator(&client, loopback(13007)); + let region_id = UUID::random().unwrap(); + let observed = Arc::new(Mutex::new(Vec::new())); + let observed_values = Arc::clone(&observed); + let manager_for_callback = Arc::clone(&manager); + let _subscription = + manager.native_subscribe_nav_mesh_status_update(Arc::new(move |event| { + let stored = manager_for_callback + .native_last_nav_mesh_status() + .expect("status is stored before the event"); + observed_values.lock().unwrap().push(( + event.message().status, + event.simulator().ip_end_point(), + stored.version, + )); + })); + let mut status = crate::messages::linden::NavMeshStatusUpdateMessage::new().unwrap(); + status.region_id = region_id; + status.status = "building".into(); + status.version = 9; + client + .network() + .dispatch_caps_event("NavMeshStatusUpdate", &status, sim.clone()); + + let stored = manager.native_last_nav_mesh_status().unwrap(); + assert_eq!(stored.region_id, region_id); + assert_eq!(stored.status, "building"); + assert_eq!(stored.version, 9); + assert_eq!( + *observed.lock().unwrap(), + [("building".into(), sim.ip_end_point(), 9)] + ); + } + + #[tokio::test(flavor = "current_thread")] + async fn fake_grid_serves_gesture_capability_and_observes_udp_actions() { + use crate::packets::{ + Packet, PacketAckPacket, PacketAckPacketPacketsBlock, PacketType, RegionHandshakePacket, + }; + use libremetaverse_types::compat::{HttpMessageHandler, HttpResponse, Uri}; + use std::collections::BTreeMap; + use std::net::UdpSocket; + use std::sync::mpsc; + + fn packet_type(bytes: &[u8]) -> Option { + let mut packet_end = i32::try_from(bytes.len()).ok()?.checked_sub(1)?; + Packet::build_packet_with_bytes_int32_bytes( + bytes.to_vec(), + &mut packet_end, + vec![0; 8192], + ) + .ok() + .map(|packet| packet.type_) + } + + fn ack(sequence: u32) -> Vec { + let mut packet = PacketAckPacket::new_with_constructor().unwrap(); + packet.packets = vec![PacketAckPacketPacketsBlock { id: sequence }]; + let mut bytes = packet.to_bytes_with_method().unwrap(); + bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED); + bytes + } + + let animation_id = UUID::random().unwrap(); + let sound_id = UUID::random().unwrap(); + let gesture_id = UUID::random().unwrap(); + let mut gesture = crate::assets::AssetGesture::new_with_constructor().unwrap(); + gesture.sequence = vec![ + crate::assets::GestureStep::Chat(crate::assets::GestureStepChat { + text: "/7 hello grid".into(), + }), + crate::assets::GestureStep::Animation(crate::assets::GestureStepAnimation { + animation_start: true, + id: animation_id, + name: "Wave".into(), + }), + crate::assets::GestureStep::Sound(crate::assets::GestureStepSound { + id: sound_id, + name: "Chime".into(), + }), + crate::assets::GestureStep::EOF(crate::assets::GestureStepEOF), + ]; + gesture.encode().unwrap(); + let gesture_bytes = gesture.asset_data().to_vec(); + + let socket = UdpSocket::bind(loopback(0)).expect("fake grid UDP socket"); + socket + .set_read_timeout(Some(Duration::from_secs(10))) + .unwrap(); + let endpoint = socket.local_addr().unwrap(); + let (udp_sender, udp_receiver) = mpsc::channel(); + let server = std::thread::spawn(move || { + let mut buffer = [0_u8; 8192]; + let (length, client_endpoint) = socket.recv_from(&mut buffer).expect("UseCircuitCode"); + assert_eq!( + packet_type(&buffer[..length]), + Some(PacketType::UseCircuitCode) + ); + let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap()); + socket.send_to(&ack(sequence), client_endpoint).unwrap(); + + let mut handshake = RegionHandshakePacket::new_with_constructor().unwrap(); + handshake.region_info.sim_name = b"Agent Test Region\0".to_vec(); + let mut handshake_bytes = handshake.to_bytes_with_method().unwrap(); + handshake_bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED); + socket.send_to(&handshake_bytes, client_endpoint).unwrap(); + + let mut observed = Vec::new(); + while observed.len() < 3 { + let (length, _) = socket.recv_from(&mut buffer).expect("agent UDP packet"); + if buffer[0] & crate::Helpers::MSG_RELIABLE != 0 { + let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap()); + socket.send_to(&ack(sequence), client_endpoint).unwrap(); + } + let Some(kind) = packet_type(&buffer[..length]) else { + continue; + }; + if matches!( + kind, + PacketType::ChatFromViewer + | PacketType::AgentAnimation + | PacketType::SoundTrigger + ) && !observed.contains(&kind) + { + observed.push(kind); + } + } + udp_sender.send(observed).unwrap(); + }); + + let requests = Arc::new(Mutex::new(Vec::new())); + let recorded = Arc::clone(&requests); + let handler = HttpMessageHandler::new(move |request, _| { + let recorded = Arc::clone(&recorded); + let gesture_bytes = gesture_bytes.clone(); + async move { + let is_seed = request.uri.0.contains("/seed"); + recorded.lock().unwrap().push(request); + let body = if is_seed { + OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([( + "ViewerAsset".into(), + OSD::Uri(Uri("https://caps.test/viewer-asset".into())), + )]))) + .unwrap() + } else { + gesture_bytes + }; + HttpResponse { + status_code: 200, + headers: BTreeMap::new(), + content_type: None, + body, + } + } + }); + let mut client = GridClient::new().unwrap(); + client.native_set_http_caps_client(crate::HttpCapsClient::new(handler).unwrap()); + client.settings().timing().login_timeout = 2_000; + let client = Arc::new(client); + let mut network = client.network(); + network.set_circuit_code(0x1234_5678); + let simulator = network + .native_connect( + endpoint, + 0x1000, + true, + Some(Uri("https://caps.test/seed".into())), + 256, + 256, + ) + .unwrap() + .unwrap(); + let caps = simulator.clone().caps.expect("seed capabilities"); + let deadline = Instant::now() + Duration::from_secs(2); + while caps.capability_uri("ViewerAsset".into()).unwrap().is_none() + && Instant::now() < deadline + { + std::thread::sleep(Duration::from_millis(2)); + } + assert_eq!( + caps.capability_uri("ViewerAsset".into()).unwrap(), + Some(Uri("https://caps.test/viewer-asset".into())), + "seed requests: {:?}", + requests.lock().unwrap() + ); + let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + manager.native_play_gesture(gesture_id, None).await.unwrap(); + + let observed = udp_receiver.recv_timeout(Duration::from_secs(3)).unwrap(); + assert_eq!( + observed, + [ + PacketType::ChatFromViewer, + PacketType::AgentAnimation, + PacketType::SoundTrigger, + ] + ); + let requests = requests.lock().unwrap(); + assert!( + requests + .iter() + .any(|request| request.uri.0 == "https://caps.test/seed") + ); + assert!(requests.iter().any(|request| { + request.uri.0 == format!("https://caps.test/viewer-asset?gesture_id={gesture_id}") + })); + drop(requests); + simulator.native_disconnect(false).unwrap(); + server.join().unwrap(); + } +} diff --git a/crates/libremetaverse/src/agent_messages.rs b/crates/libremetaverse/src/agent_messages.rs new file mode 100644 index 0000000..8ec7049 --- /dev/null +++ b/crates/libremetaverse/src/agent_messages.rs @@ -0,0 +1,1213 @@ +//! 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() + } +} diff --git a/crates/libremetaverse/src/caps.rs b/crates/libremetaverse/src/caps.rs index fae3b16..56de328 100644 --- a/crates/libremetaverse/src/caps.rs +++ b/crates/libremetaverse/src/caps.rs @@ -256,6 +256,12 @@ impl Caps { Ok(read(&self.inner.caps).get(&capability).cloned()) } + pub(crate) fn seed_request_finished(&self) -> bool { + mutex(&self.inner.seed_task) + .as_ref() + .is_none_or(|task| task.handle.is_finished()) + } + pub fn disconnect(&self, immediate: bool) -> Result<(), Error> { self.inner.disconnect(immediate); Ok(()) diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index d2157ba..874bfbe 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -117,6 +117,7 @@ struct ClientRuntime { http_caps_client: Mutex, caps_rate_limiter: Mutex, network_manager: Mutex>, + agent_manager: Mutex>, shutdown_complete: Condvar, shutdown_wait: Mutex<()>, } @@ -144,6 +145,7 @@ impl ClientRuntime { http_caps_client: Mutex::new(http_caps_client), caps_rate_limiter: Mutex::new(caps_rate_limiter), network_manager: Mutex::new(std::sync::Weak::new()), + agent_manager: Mutex::new(std::sync::Weak::new()), shutdown_complete: Condvar::new(), shutdown_wait: Mutex::new(()), } @@ -242,11 +244,24 @@ impl ClientRuntime { /// Construction is side-effect free: it creates no runtime, task, socket, or /// HTTP request. Network-facing services are attached explicitly and are owned /// until ordered shutdown or drop. -#[derive(Clone)] pub struct GridClient { pub(crate) settings: Settings, pub(crate) time_provider: TimeProvider, runtime: Arc, + network_manager: Option, + agent_manager: Option>, +} + +impl Clone for GridClient { + fn clone(&self) -> Self { + Self { + settings: self.settings.clone(), + time_provider: self.time_provider.clone(), + runtime: Arc::clone(&self.runtime), + network_manager: self.network_manager.clone(), + agent_manager: None, + } + } } #[cfg(test)] @@ -353,6 +368,9 @@ impl GridClient { } pub(crate) fn native_network(&self) -> Result { + if let Some(manager) = &self.network_manager { + return Ok(manager.clone()); + } let mut cached = self .runtime .network_manager @@ -369,12 +387,44 @@ impl GridClient { } #[allow(clippy::needless_pass_by_value)] // The mapped C# property setter owns its value. - pub(crate) fn native_set_network(&self, value: crate::NetworkManager) { + pub(crate) fn native_set_network(&mut self, value: crate::NetworkManager) { *self .runtime .network_manager .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = value.native_inner_weak(); + self.network_manager = Some(value); + } + + pub(crate) fn native_self(&mut self) -> &mut crate::agent_manager::AgentManager { + if self.agent_manager.is_none() { + let client = Arc::new(self.clone()); + let manager = crate::agent_manager::AgentManager::native_new(Some(client)) + .unwrap_or_else(|_| panic!("failed to construct AgentManager")); + self.agent_manager = Some(Box::new(manager)); + } + self.agent_manager.as_deref_mut().expect("agent manager") + } + + pub(crate) fn cached_agent_manager_inner( + &self, + ) -> Option> { + self.runtime + .agent_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .upgrade() + } + + pub(crate) fn cache_agent_manager_inner( + &self, + inner: &Arc, + ) { + *self + .runtime + .agent_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Arc::downgrade(inner); } pub(crate) fn native_set_caps_rate_limiter( @@ -416,7 +466,7 @@ impl fmt::Debug for GridClient { .field("lifecycle_state", &self.lifecycle_state()) .field("services", &service_names) .field("time_provider", &self.time_provider) - .finish() + .finish_non_exhaustive() } } @@ -527,6 +577,8 @@ impl GridClientBuilder { http_caps_client, caps_rate_limiter, )), + network_manager: None, + agent_manager: None, }) } } diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index 9ba8f06..a149ce0 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -134,23 +134,10 @@ impl AccessList { pub use crate::login::AccountLevelBenefits; /// C# type: `T:LibreMetaverse.AgentAccessEventArgs`. -pub struct AgentAccessEventArgs; -impl AgentAccessEventArgs { - /// C# member: `M:LibreMetaverse.AgentAccessEventArgs.#ctor(System.Boolean,System.String)`. - pub fn new(success: bool, new_level: String) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentAccessEventArgs.#ctor(System.Boolean,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.AgentAccessEventArgs.NewLevel`. - pub fn new_level(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentAccessEventArgs.NewLevel") - } - /// C# member: `P:LibreMetaverse.AgentAccessEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentAccessEventArgs.Success") - } -} +/// C# member: `M:LibreMetaverse.AgentAccessEventArgs.#ctor(System.Boolean,System.String)`. +/// C# member: `P:LibreMetaverse.AgentAccessEventArgs.NewLevel`. +/// C# member: `P:LibreMetaverse.AgentAccessEventArgs.Success`. +pub use crate::agent_messages::AgentAccessEventArgs; /// C# type: `T:LibreMetaverse.AgentCachedBakesReplyEventArgs`. pub struct AgentCachedBakesReplyEventArgs; @@ -164,58 +151,14 @@ impl AgentCachedBakesReplyEventArgs { } /// C# type: `T:LibreMetaverse.AgentDataReplyEventArgs`. -pub struct AgentDataReplyEventArgs; -impl AgentDataReplyEventArgs { - /// C# member: `M:LibreMetaverse.AgentDataReplyEventArgs.#ctor(System.String,System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.GroupPowers,System.String)`. - pub fn new( - first_name: String, - last_name: String, - active_group_id: libremetaverse_types::UUID, - group_title: String, - group_powers: libremetaverse::GroupPowers, - group_name: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentDataReplyEventArgs.#ctor(System.String,System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.GroupPowers,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.ActiveGroupID`. - pub fn active_group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentDataReplyEventArgs.ActiveGroupID" - ) - } - /// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.FirstName`. - pub fn first_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentDataReplyEventArgs.FirstName" - ) - } - /// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.GroupName`. - pub fn group_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentDataReplyEventArgs.GroupName" - ) - } - /// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.GroupPowers`. - pub fn group_powers(&self) -> libremetaverse::GroupPowers { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentDataReplyEventArgs.GroupPowers" - ) - } - /// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.GroupTitle`. - pub fn group_title(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentDataReplyEventArgs.GroupTitle" - ) - } - /// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.LastName`. - pub fn last_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentDataReplyEventArgs.LastName" - ) - } -} +/// C# member: `M:LibreMetaverse.AgentDataReplyEventArgs.#ctor(System.String,System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.GroupPowers,System.String)`. +/// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.ActiveGroupID`. +/// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.FirstName`. +/// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.GroupName`. +/// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.GroupPowers`. +/// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.GroupTitle`. +/// C# member: `P:LibreMetaverse.AgentDataReplyEventArgs.LastName`. +pub use crate::agent_messages::AgentDataReplyEventArgs; /// C# type: `T:LibreMetaverse.AgentDisplayName`. /// C# member: `M:LibreMetaverse.AgentDisplayName.#ctor`. @@ -234,23 +177,9 @@ impl AgentDataReplyEventArgs { pub use crate::message_decoder::AgentDisplayName; /// C# type: `T:LibreMetaverse.AgentExperiencesEventArgs`. -pub struct AgentExperiencesEventArgs; -impl AgentExperiencesEventArgs { - /// C# member: `M:LibreMetaverse.AgentExperiencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperienceListMessage)`. - pub fn new( - agent_experiences: libremetaverse::messages::linden::ExperienceListMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentExperiencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperienceListMessage)", - ) - } - /// C# member: `P:LibreMetaverse.AgentExperiencesEventArgs.AgentExperiences`. - pub fn agent_experiences(&self) -> libremetaverse::messages::linden::ExperienceListMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentExperiencesEventArgs.AgentExperiences" - ) - } -} +/// C# member: `M:LibreMetaverse.AgentExperiencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperienceListMessage)`. +/// C# member: `P:LibreMetaverse.AgentExperiencesEventArgs.AgentExperiences`. +pub use crate::agent_messages::AgentExperiencesEventArgs; /// C# type: `T:LibreMetaverse.AgentFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -264,24 +193,12 @@ impl AgentFlags { } /// C# type: `T:LibreMetaverse.AgentManager`. -pub struct AgentManager { - /// C# member: `F:LibreMetaverse.AgentManager.AgentStateStatus`. - pub agent_state_status: Option, - /// C# member: `F:LibreMetaverse.AgentManager.GroupChatSessions`. - pub group_chat_sessions: std::sync::RwLock< - std::collections::HashMap< - libremetaverse_types::UUID, - Vec, - >, - >, - /// C# member: `F:LibreMetaverse.AgentManager.Movement`. - pub movement: libremetaverse::AgentManagerAgentMovement, - /// C# member: `F:LibreMetaverse.AgentManager.MuteList`. - pub mute_list: std::sync::RwLock>, - /// C# member: `F:LibreMetaverse.AgentManager.SignaledAnimations`. - pub signaled_animations: - std::sync::RwLock>, -} +pub use crate::agent_manager::AgentManager; +/// C# member: `F:LibreMetaverse.AgentManager.AgentStateStatus`. +/// C# member: `F:LibreMetaverse.AgentManager.GroupChatSessions`. +/// C# member: `F:LibreMetaverse.AgentManager.Movement`. +/// C# member: `F:LibreMetaverse.AgentManager.MuteList`. +/// C# member: `F:LibreMetaverse.AgentManager.SignaledAnimations`. impl AgentManager { /// C# member: `E:LibreMetaverse.AgentManager.AgentDataReply`. pub fn subscribe_agent_data_reply( @@ -290,7 +207,7 @@ impl AgentManager { libremetaverse::AgentDataReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.AgentDataReply") + self.native_subscribe_agent_data_reply(handler) } /// C# member: `E:LibreMetaverse.AgentManager.AgentExperiencesUpdated`. pub fn subscribe_agent_experiences_updated( @@ -299,9 +216,7 @@ impl AgentManager { libremetaverse::AgentExperiencesEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.AgentExperiencesUpdated" - ) + self.native_subscribe_agent_experiences_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.AgentPreferencesUpdated`. pub fn subscribe_agent_preferences_updated( @@ -310,9 +225,7 @@ impl AgentManager { libremetaverse::AgentPreferencesEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.AgentPreferencesUpdated" - ) + self.native_subscribe_agent_preferences_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.AlertMessage`. pub fn subscribe_alert_message( @@ -328,7 +241,7 @@ impl AgentManager { libremetaverse::AnimationsChangedEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.AnimationsChanged") + self.native_subscribe_animations_changed(handler) } /// C# member: `E:LibreMetaverse.AgentManager.AvatarRenderInfoUpdated`. pub fn subscribe_avatar_render_info_updated( @@ -337,9 +250,7 @@ impl AgentManager { libremetaverse::AvatarRenderInfoEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.AvatarRenderInfoUpdated" - ) + self.native_subscribe_avatar_render_info_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.AvatarSitResponse`. pub fn subscribe_avatar_sit_response( @@ -364,7 +275,7 @@ impl AgentManager { &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.ChatFromSimulator") + self.native_subscribe_chat_from_simulator(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ChatSessionMemberAdded`. pub fn subscribe_chat_session_member_added( @@ -373,9 +284,7 @@ impl AgentManager { libremetaverse::ChatSessionMemberAddedEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.ChatSessionMemberAdded" - ) + self.native_subscribe_chat_session_member_added(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ChatSessionMemberLeft`. pub fn subscribe_chat_session_member_left( @@ -384,9 +293,7 @@ impl AgentManager { libremetaverse::ChatSessionMemberLeftEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.ChatSessionMemberLeft" - ) + self.native_subscribe_chat_session_member_left(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ExperienceInfoReceived`. pub fn subscribe_experience_info_received( @@ -395,9 +302,7 @@ impl AgentManager { libremetaverse::ExperienceInfoEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.ExperienceInfoReceived" - ) + self.native_subscribe_experience_info_received(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ExperiencePreferencesUpdated`. pub fn subscribe_experience_preferences_updated( @@ -406,9 +311,7 @@ impl AgentManager { libremetaverse::ExperiencePreferencesEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.ExperiencePreferencesUpdated" - ) + self.native_subscribe_experience_preferences_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.GroupChatJoined`. pub fn subscribe_group_chat_joined( @@ -417,7 +320,7 @@ impl AgentManager { libremetaverse::GroupChatJoinedEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.GroupChatJoined") + self.native_subscribe_group_chat_joined(handler) } /// C# member: `E:LibreMetaverse.AgentManager.IM`. pub fn subscribe_im( @@ -426,7 +329,7 @@ impl AgentManager { libremetaverse::InstantMessageEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.IM") + self.native_subscribe_im(handler) } /// C# member: `E:LibreMetaverse.AgentManager.LoadURL`. pub fn subscribe_load_url( @@ -447,7 +350,7 @@ impl AgentManager { &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.MoneyBalance") + self.native_subscribe_money_balance(handler) } /// C# member: `E:LibreMetaverse.AgentManager.MoneyBalanceReply`. pub fn subscribe_money_balance_reply( @@ -456,14 +359,14 @@ impl AgentManager { libremetaverse::MoneyBalanceReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.MoneyBalanceReply") + self.native_subscribe_money_balance_reply(handler) } /// C# member: `E:LibreMetaverse.AgentManager.MuteListUpdated`. pub fn subscribe_mute_list_updated( &self, handler: libremetaverse_types::compat::EventHandler<()>, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.MuteListUpdated") + self.native_subscribe_mute_list_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.NavMeshStatusUpdate`. pub fn subscribe_nav_mesh_status_update( @@ -472,16 +375,14 @@ impl AgentManager { libremetaverse::NavMeshStatusUpdateEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.NavMeshStatusUpdate" - ) + self.native_subscribe_nav_mesh_status_update(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ProductInfoUpdated`. pub fn subscribe_product_info_updated( &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.ProductInfoUpdated") + self.native_subscribe_product_info_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.RegionCrossed`. pub fn subscribe_region_crossed( @@ -508,9 +409,7 @@ impl AgentManager { libremetaverse::RegionExperiencesEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.RegionExperiencesUpdated" - ) + self.native_subscribe_region_experiences_updated(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ScriptControlChange`. pub fn subscribe_script_control_change( @@ -553,9 +452,7 @@ impl AgentManager { libremetaverse::SetDisplayNameReplyEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.SetDisplayNameReply" - ) + self.native_subscribe_set_display_name_reply(handler) } /// C# member: `E:LibreMetaverse.AgentManager.TeleportProgress`. pub fn subscribe_teleport_progress( @@ -571,9 +468,7 @@ impl AgentManager { libremetaverse::ViewerBenefitsEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.ViewerBenefitsUpdated" - ) + self.native_subscribe_viewer_benefits_updated(handler) } /// C# member: `F:LibreMetaverse.AgentManager.MaxChatMessageSize`. pub const MAX_CHAT_MESSAGE_SIZE: i32 = 1023; @@ -595,9 +490,7 @@ impl AgentManager { pub fn new( client: Option>, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.#ctor(LibreMetaverse.GridClient)", - ) + crate::agent_manager::AgentManager::native_new(client) } /// C# member: `M:LibreMetaverse.AgentManager.ActivateGesture(LibreMetaverse.UUID,LibreMetaverse.UUID)`. pub fn activate_gesture( @@ -605,9 +498,7 @@ impl AgentManager { inv_id: libremetaverse_types::UUID, asset_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.ActivateGesture(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) + self.native_activate_gesture(inv_id, asset_id) } /// C# member: `M:LibreMetaverse.AgentManager.Animate(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.Boolean},System.Boolean)`. pub fn animate( @@ -615,9 +506,7 @@ impl AgentManager { animations: std::collections::HashMap, reliable: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.Animate(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.Boolean},System.Boolean)", - ) + self.native_animate(animations, reliable) } /// C# member: `M:LibreMetaverse.AgentManager.AnimationStart(LibreMetaverse.UUID,System.Boolean)`. pub fn animation_start( @@ -625,9 +514,7 @@ impl AgentManager { animation: libremetaverse_types::UUID, reliable: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AnimationStart(LibreMetaverse.UUID,System.Boolean)", - ) + self.native_animation_start(animation, reliable) } /// C# member: `M:LibreMetaverse.AgentManager.AnimationStop(LibreMetaverse.UUID,System.Boolean)`. pub fn animation_stop( @@ -635,9 +522,7 @@ impl AgentManager { animation: libremetaverse_types::UUID, reliable: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AnimationStop(LibreMetaverse.UUID,System.Boolean)", - ) + self.native_animation_stop(animation, reliable) } /// C# member: `M:LibreMetaverse.AgentManager.AutoPilot(System.Double,System.Double,System.Double)`. pub fn auto_pilot_with_double_double_double( @@ -681,8 +566,15 @@ impl AgentManager { duration: f32, effect_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.BeamEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)", + self.native_viewer_effect( + source_avatar, + target_object, + global_offset, + color, + duration, + effect_id, + crate::EffectType::Beam, + None, ) } /// C# member: `M:LibreMetaverse.AgentManager.CancelCrossing`. @@ -697,9 +589,7 @@ impl AgentManager { type_: libremetaverse::ChatType, split_large_messages: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.Chat(System.String,System.Int32,LibreMetaverse.ChatType,System.Boolean)", - ) + self.native_chat(message, channel, type_, split_large_messages) } /// C# member: `M:LibreMetaverse.AgentManager.ChatterBoxAcceptInviteAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn chatter_box_accept_invite( @@ -707,9 +597,8 @@ impl AgentManager { session_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.ChatterBoxAcceptInviteAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_chatter_box_accept_invite(session_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.CompleteAgentMovement(LibreMetaverse.Simulator)`. pub fn complete_agent_movement( @@ -750,22 +639,18 @@ impl AgentManager { &self, inv_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.DeactivateGesture(LibreMetaverse.UUID)", - ) + self.native_deactivate_gesture(inv_id) } /// C# member: `M:LibreMetaverse.AgentManager.DeleteClassified(LibreMetaverse.UUID)`. pub fn delete_classified( &self, classified_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.DeleteClassified(LibreMetaverse.UUID)", - ) + self.native_delete_classified(classified_id) } /// C# member: `M:LibreMetaverse.AgentManager.Dispose`. pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.Dispose") + self.native_dispose() } /// C# member: `M:LibreMetaverse.AgentManager.FindExperienceByNameAsync(System.String,System.Int32,System.Int32,System.Threading.CancellationToken)`. pub async fn find_experience_by_name( @@ -775,9 +660,8 @@ impl AgentManager { page_size: Option, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.FindExperienceByNameAsync(System.String,System.Int32,System.Int32,System.Threading.CancellationToken)", - ) + self.native_find_experience_by_name(query, page, page_size, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.Fly(System.Boolean)`. pub fn fly(&self, start: bool) -> Result<(), crate::Error> { @@ -789,18 +673,15 @@ impl AgentManager { experience_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.ForgetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_forget_experience_permission(experience_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetAdminExperiencesAsync(System.Threading.CancellationToken)`. pub async fn get_admin_experiences( &self, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetAdminExperiencesAsync(System.Threading.CancellationToken)", - ) + self.native_get_admin_experiences(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GetAgentExperiencePermissionsAsync(System.Threading.CancellationToken)`. pub async fn get_agent_experience_permissions( @@ -808,18 +689,15 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetAgentExperiencePermissionsAsync(System.Threading.CancellationToken)", - ) + self.native_get_agent_experience_permissions(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetAgentExperiencesAsync(System.Threading.CancellationToken)`. pub async fn get_agent_experiences( &self, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetAgentExperiencesAsync(System.Threading.CancellationToken)", - ) + self.native_get_agent_experiences(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GetAgentPreferencesAsync(System.Threading.CancellationToken)`. pub async fn get_agent_preferences( @@ -827,9 +705,7 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetAgentPreferencesAsync(System.Threading.CancellationToken)", - ) + self.native_get_agent_preferences(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GetAttachmentResourcesAsync(System.Threading.CancellationToken)`. pub async fn get_attachment_resources( @@ -842,9 +718,8 @@ impl AgentManager { ), crate::Error, > { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetAttachmentResourcesAsync(System.Threading.CancellationToken)", - ) + self.native_get_attachment_resources(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetAvatarRenderInfoAsync(System.Threading.CancellationToken)`. pub async fn get_avatar_render_info( @@ -852,18 +727,15 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetAvatarRenderInfoAsync(System.Threading.CancellationToken)", - ) + self.native_get_avatar_render_info(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GetCreatorExperiencesAsync(System.Threading.CancellationToken)`. pub async fn get_creator_experiences( &self, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetCreatorExperiencesAsync(System.Threading.CancellationToken)", - ) + self.native_get_creator_experiences(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetCrossingDetails`. pub fn get_crossing_details(&self) -> Result { @@ -889,9 +761,8 @@ impl AgentManager { experience_ids: Box>, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetExperienceInfoAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_get_experience_info(experience_ids, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn get_experience_permission( @@ -899,9 +770,8 @@ impl AgentManager { experience_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_get_experience_permission(experience_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetExperiencePreferencesAsync(System.Threading.CancellationToken)`. pub async fn get_experience_preferences( @@ -909,9 +779,8 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetExperiencePreferencesAsync(System.Threading.CancellationToken)", - ) + self.native_get_experience_preferences(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetGroupExperiencesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn get_group_experiences( @@ -919,9 +788,8 @@ impl AgentManager { group_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetGroupExperiencesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_get_group_experiences(group_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.GetProductInfoAsync(System.Threading.CancellationToken)`. pub async fn get_product_info( @@ -929,9 +797,7 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetProductInfoAsync(System.Threading.CancellationToken)", - ) + self.native_get_product_info(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GetRegionExperiencesAsync(System.Threading.CancellationToken)`. pub async fn get_region_experiences( @@ -939,9 +805,7 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetRegionExperiencesAsync(System.Threading.CancellationToken)", - ) + self.native_get_region_experiences(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GetSimulatorsForObject(LibreMetaverse.UUID)`. pub fn get_simulators_for_object( @@ -957,9 +821,7 @@ impl AgentManager { &self, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetViewerBenefitsAsync(System.Threading.CancellationToken)", - ) + self.native_get_viewer_benefits(cancellation_token).await } /// C# member: `M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32)`. pub fn give_avatar_money_with_uuid_int32( @@ -967,9 +829,7 @@ impl AgentManager { target: libremetaverse_types::UUID, amount: i32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32)", - ) + self.native_give_avatar_money(target, amount, String::new()) } /// C# member: `M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32,System.String)`. pub fn give_avatar_money_with_uuid_int32_string( @@ -978,9 +838,7 @@ impl AgentManager { amount: i32, description: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32,System.String)", - ) + self.native_give_avatar_money(target, amount, description) } /// C# member: `M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32)`. pub fn give_group_money_with_uuid_int32( @@ -988,9 +846,7 @@ impl AgentManager { target: libremetaverse_types::UUID, amount: i32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32)", - ) + self.native_give_group_money(target, amount, String::new()) } /// C# member: `M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32,System.String)`. pub fn give_group_money_with_uuid_int32_string( @@ -999,9 +855,7 @@ impl AgentManager { amount: i32, description: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32,System.String)", - ) + self.native_give_group_money(target, amount, description) } /// C# member: `M:LibreMetaverse.AgentManager.GiveMoney(LibreMetaverse.UUID,System.Int32,System.String,LibreMetaverse.MoneyTransactionType,LibreMetaverse.TransactionFlags)`. pub fn give_money( @@ -1012,9 +866,7 @@ impl AgentManager { type_: libremetaverse::MoneyTransactionType, flags: libremetaverse::TransactionFlags, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GiveMoney(LibreMetaverse.UUID,System.Int32,System.String,LibreMetaverse.MoneyTransactionType,LibreMetaverse.TransactionFlags)", - ) + self.native_give_money(target, amount, description, type_, flags) } /// C# member: `M:LibreMetaverse.AgentManager.GiveObjectMoney(LibreMetaverse.UUID,System.Int32,System.String)`. pub fn give_object_money( @@ -1023,9 +875,7 @@ impl AgentManager { amount: i32, object_name: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GiveObjectMoney(LibreMetaverse.UUID,System.Int32,System.String)", - ) + self.native_give_object_money(target, amount, object_name) } /// C# member: `M:LibreMetaverse.AgentManager.GoHomeAsync(System.Threading.CancellationToken)`. pub async fn go_home( @@ -1090,9 +940,7 @@ impl AgentManager { im_session_id: libremetaverse_types::UUID, accept: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GroupInviteRespond(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)", - ) + self.native_group_invite_respond(group_id, im_session_id, accept) } /// C# member: `M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String)`. pub fn instant_message_with_uuid_string( @@ -1100,9 +948,7 @@ impl AgentManager { target: libremetaverse_types::UUID, message: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String)", - ) + self.native_instant_message(target, message, None) } /// C# member: `M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)`. pub fn instant_message_with_uuid_string_uuid( @@ -1111,9 +957,7 @@ impl AgentManager { message: String, im_session_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)", - ) + self.native_instant_message(target, message, Some(im_session_id)) } /// C# member: `M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.InstantMessageDialog,LibreMetaverse.InstantMessageOnline,LibreMetaverse.Vector3,LibreMetaverse.UUID,System.Byte[])`. pub fn instant_message_with_string_uuid_string_uuid_instant_message_dialog_instant_message_online_vector3_uuid_bytes( @@ -1128,8 +972,16 @@ impl AgentManager { region_id: libremetaverse_types::UUID, binary_bucket: Vec, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.InstantMessageDialog,LibreMetaverse.InstantMessageOnline,LibreMetaverse.Vector3,LibreMetaverse.UUID,System.Byte[])", + self.native_instant_message_full( + from_name, + target, + message, + im_session_id, + dialog, + offline, + position, + region_id, + binary_bucket, ) } /// C# member: `M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID[])`. @@ -1141,8 +993,12 @@ impl AgentManager { im_session_id: libremetaverse_types::UUID, conference_i_ds: Vec, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID[])", + self.native_instant_message_conference( + from_name, + target, + message, + im_session_id, + conference_i_ds, ) } /// C# member: `M:LibreMetaverse.AgentManager.InstantMessageGroup(LibreMetaverse.UUID,System.String)`. @@ -1151,9 +1007,7 @@ impl AgentManager { group_id: libremetaverse_types::UUID, message: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.InstantMessageGroup(LibreMetaverse.UUID,System.String)", - ) + self.native_instant_message_group(None, group_id, message) } /// C# member: `M:LibreMetaverse.AgentManager.InstantMessageGroup(System.String,LibreMetaverse.UUID,System.String)`. pub fn instant_message_group_with_string_uuid_string( @@ -1162,9 +1016,7 @@ impl AgentManager { group_id: libremetaverse_types::UUID, message: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.InstantMessageGroup(System.String,LibreMetaverse.UUID,System.String)", - ) + self.native_instant_message_group(Some(from_name), group_id, message) } /// C# member: `M:LibreMetaverse.AgentManager.IsCrossing`. pub fn is_crossing(&self) -> Result { @@ -1176,9 +1028,8 @@ impl AgentManager { experience_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.IsExperienceAdminAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_is_experience_role("IsExperienceAdmin", experience_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.IsExperienceContributorAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn is_experience_contributor( @@ -1186,18 +1037,15 @@ impl AgentManager { experience_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.IsExperienceContributorAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_is_experience_role("IsExperienceContributor", experience_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.IsGroupMessage(LibreMetaverse.InstantMessage)`. pub fn is_group_message( &self, message: libremetaverse::InstantMessage, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.IsGroupMessage(LibreMetaverse.InstantMessage)", - ) + Ok(self.native_is_group_message(message)) } /// C# member: `M:LibreMetaverse.AgentManager.IsNearBorder(LibreMetaverse.Vector3,System.UInt32,System.UInt32,System.Single)`. pub fn is_near_border( @@ -1224,8 +1072,12 @@ impl AgentManager { type_: libremetaverse::LookAtType, effect_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.LookAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.LookAtType,LibreMetaverse.UUID)", + self.native_look_at_effect( + source_avatar, + target_object, + global_offset, + type_, + effect_id, ) } /// C# member: `M:LibreMetaverse.AgentManager.ModerateChatSessions(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Boolean,System.Threading.CancellationToken)`. @@ -1237,31 +1089,23 @@ impl AgentManager { moderate: bool, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.ModerateChatSessions(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Boolean,System.Threading.CancellationToken)", - ) + self.native_moderate_chat_sessions(session_id, member_id, key, moderate, cancellation_token) } /// C# member: `M:LibreMetaverse.AgentManager.PayUploadFee`. pub fn pay_upload_fee_with_method(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.PayUploadFee") + self.native_pay_upload_fee(None, String::new()) } /// C# member: `M:LibreMetaverse.AgentManager.PayUploadFee(System.Int32)`. pub fn pay_upload_fee_with_int32(&self, amount: i32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PayUploadFee(System.Int32)", - ) + self.native_pay_upload_fee(Some(amount), String::new()) } /// C# member: `M:LibreMetaverse.AgentManager.PayUploadFee(System.String)`. pub fn pay_upload_fee_with_string(&self, description: String) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PayUploadFee(System.String)", - ) + self.native_pay_upload_fee(None, description) } /// C# member: `M:LibreMetaverse.AgentManager.PickDelete(LibreMetaverse.UUID)`. pub fn pick_delete(&self, pick_id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PickDelete(LibreMetaverse.UUID)", - ) + self.native_pick_delete(pick_id) } /// C# member: `M:LibreMetaverse.AgentManager.PickInfoUpdate(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID,System.String,LibreMetaverse.Vector3d,LibreMetaverse.UUID,System.String)`. pub fn pick_info_update( @@ -1274,8 +1118,14 @@ impl AgentManager { texture_id: libremetaverse_types::UUID, description: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PickInfoUpdate(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID,System.String,LibreMetaverse.Vector3d,LibreMetaverse.UUID,System.String)", + self.native_pick_info_update( + pick_id, + top_pick, + parcel_id, + name, + global_position, + texture_id, + description, ) } /// C# member: `M:LibreMetaverse.AgentManager.PlayGestureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. @@ -1284,9 +1134,8 @@ impl AgentManager { gesture_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PlayGestureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_play_gesture(gesture_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.PointAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.PointAtType,LibreMetaverse.UUID)`. pub fn point_at_effect( @@ -1297,8 +1146,12 @@ impl AgentManager { type_: libremetaverse::PointAtType, effect_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PointAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.PointAtType,LibreMetaverse.UUID)", + self.native_point_at_effect( + source_avatar, + target_object, + global_offset, + type_, + effect_id, ) } /// C# member: `M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.ValueTuple{System.Int32,System.Boolean}},System.Threading.CancellationToken)`. @@ -1307,9 +1160,8 @@ impl AgentManager { agents: std::collections::HashMap, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.ValueTuple{System.Int32,System.Boolean}},System.Threading.CancellationToken)", - ) + self.native_post_avatar_render_info(agents, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Int32,System.Boolean,System.Threading.CancellationToken)`. pub async fn post_avatar_render_info_with_int32_boolean_cancellation_token( @@ -1318,9 +1170,8 @@ impl AgentManager { too_complex: bool, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Int32,System.Boolean,System.Threading.CancellationToken)", - ) + self.native_post_local_avatar_render_info(weight, too_complex, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.QueryExperiencesOnParcelAsync(System.Int32,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub async fn query_experiences_on_parcel( @@ -1330,9 +1181,8 @@ impl AgentManager { cancellation_token: Option, ) -> Result>, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.QueryExperiencesOnParcelAsync(System.Int32,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)", - ) + self.native_query_experiences_on_parcel(parcel_local_id, experience_ids, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.RemoveMuteListEntry(LibreMetaverse.UUID,System.String)`. pub fn remove_mute_list_entry( @@ -1340,9 +1190,7 @@ impl AgentManager { id: libremetaverse_types::UUID, name: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RemoveMuteListEntry(LibreMetaverse.UUID,System.String)", - ) + self.native_remove_mute_list_entry(id, name) } /// C# member: `M:LibreMetaverse.AgentManager.ReplyToScriptDialog(System.Int32,System.Int32,System.String,LibreMetaverse.UUID)`. pub fn reply_to_script_dialog( @@ -1352,35 +1200,29 @@ impl AgentManager { button_label: String, object_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.ReplyToScriptDialog(System.Int32,System.Int32,System.String,LibreMetaverse.UUID)", - ) + self.native_reply_to_script_dialog(channel, button_index, button_label, object_id) } /// C# member: `M:LibreMetaverse.AgentManager.RequestBalance`. pub fn request_balance(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.RequestBalance") + self.native_request_balance() } /// C# member: `M:LibreMetaverse.AgentManager.RequestJoinGroupChat(LibreMetaverse.UUID)`. pub fn request_join_group_chat( &self, group_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestJoinGroupChat(LibreMetaverse.UUID)", - ) + self.native_join_group_chat(group_id) } /// C# member: `M:LibreMetaverse.AgentManager.RequestLeaveGroupChat(LibreMetaverse.UUID)`. pub fn request_leave_group_chat( &self, group_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestLeaveGroupChat(LibreMetaverse.UUID)", - ) + self.native_leave_group_chat(group_id) } /// C# member: `M:LibreMetaverse.AgentManager.RequestMuteList`. pub fn request_mute_list(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.RequestMuteList") + self.native_request_mute_list() } /// C# member: `M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)`. pub async fn request_nav_mesh_generation_status( @@ -1388,18 +1230,16 @@ impl AgentManager { cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)", - ) + self.native_request_nav_mesh_generation_status(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)`. pub async fn request_nav_mesh_rebake( &self, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)", - ) + self.native_request_nav_mesh_rebake(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.RequestScriptSensor(System.String,LibreMetaverse.UUID,LibreMetaverse.ScriptSensorTypeFlags,System.Single,System.Single,LibreMetaverse.UUID,LibreMetaverse.Simulator)`. pub fn request_script_sensor( @@ -1462,9 +1302,8 @@ impl AgentManager { &self, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RetrieveInstantMessagesAsync(System.Threading.CancellationToken)", - ) + self.native_retrieve_instant_messages(cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.ScriptQuestionReply(LibreMetaverse.Simulator,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.ScriptPermission)`. pub fn script_question_reply( @@ -1489,9 +1328,16 @@ impl AgentManager { global_position: libremetaverse_types::Vector3, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendPostcardAsync(System.Byte[],System.String,System.String,System.String,System.String,LibreMetaverse.Vector3,System.Threading.CancellationToken)", + self.native_send_postcard( + jpeg_image_data, + to_email, + from_name, + subject, + message, + global_position, + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.AgentManager.SendTeleportLure(LibreMetaverse.UUID)`. pub fn send_teleport_lure_with_uuid( @@ -1548,9 +1394,8 @@ impl AgentManager { stats: libremetaverse::messages::linden::ViewerStatsMessage, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendViewerStatsAsync(LibreMetaverse.Messages.Linden.ViewerStatsMessage,System.Threading.CancellationToken)", - ) + self.native_send_viewer_stats(stats, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.SetAgentAccessAsync(System.String,System.Threading.CancellationToken)`. pub async fn set_agent_access_with_string_cancellation_token( @@ -1558,9 +1403,8 @@ impl AgentManager { access: String, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SetAgentAccessAsync(System.String,System.Threading.CancellationToken)", - ) + self.native_set_agent_access_async(access, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.SetDisplayNameAsync(System.String,System.String,System.Threading.CancellationToken)`. pub async fn set_display_name( @@ -1569,9 +1413,8 @@ impl AgentManager { new_name: String, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SetDisplayNameAsync(System.String,System.String,System.Threading.CancellationToken)", - ) + self.native_set_display_name(old_name, new_name, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.SetExperiencePermissionAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub async fn set_experience_permission( @@ -1580,9 +1423,8 @@ impl AgentManager { permission: String, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SetExperiencePermissionAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) + self.native_set_experience_permission(experience_id, permission, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.SetExperiencePreferencesAsync(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage,System.Threading.CancellationToken)`. pub async fn set_experience_preferences_with_experience_preferences_message_cancellation_token( @@ -1590,9 +1432,8 @@ impl AgentManager { preferences: libremetaverse::messages::linden::ExperiencePreferencesMessage, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SetExperiencePreferencesAsync(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage,System.Threading.CancellationToken)", - ) + self.native_set_experience_preferences_async(preferences, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.SetHeightWidth(System.UInt16,System.UInt16)`. pub fn set_height_width(&self, height: u16, width: u16) -> Result<(), crate::Error> { @@ -1610,9 +1451,8 @@ impl AgentManager { hover_height: f64, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SetHoverHeightAsync(System.Double,System.Threading.CancellationToken)", - ) + self.native_set_hover_height_async(hover_height, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.Sit`. pub fn sit(&self) -> Result<(), crate::Error> { @@ -1630,8 +1470,15 @@ impl AgentManager { duration: f32, effect_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SphereEffect(LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)", + self.native_viewer_effect( + libremetaverse_types::UUID::zero(), + libremetaverse_types::UUID::zero(), + global_offset, + color, + duration, + effect_id, + crate::EffectType::Sphere, + None, ) } /// C# member: `M:LibreMetaverse.AgentManager.Stand`. @@ -1645,9 +1492,8 @@ impl AgentManager { tmp_session_id: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.StartIMConferenceAsync(System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_start_im_conference(participants, tmp_session_id, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.TeleportAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn teleport_with_uuid_cancellation_token( @@ -1758,9 +1604,8 @@ impl AgentManager { is_public: bool, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateAgentLanguageAsync(System.String,System.Boolean,System.Threading.CancellationToken)", - ) + self.native_update_agent_language(language, is_public, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,LibreMetaverse.Vector3d,System.String,System.String,System.Boolean)`. pub fn update_classified_info_with_uuid_classified_categories_uuid_int32_vector3d_string_string_boolean( @@ -1774,8 +1619,15 @@ impl AgentManager { desc: String, auto_renew: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,LibreMetaverse.Vector3d,System.String,System.String,System.Boolean)", + self.native_update_classified_info( + classified_id, + category, + snapshot_id, + price, + position, + name, + desc, + auto_renew, ) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,System.String,System.String,System.Boolean)`. @@ -1789,8 +1641,15 @@ impl AgentManager { desc: String, auto_renew: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,System.String,System.String,System.Boolean)", + self.native_update_classified_info( + classified_id, + category, + snapshot_id, + price, + libremetaverse_types::Vector3d::zero(), + name, + desc, + auto_renew, ) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateExperienceAsync(LibreMetaverse.Messages.Linden.ExperienceInfo,System.Threading.CancellationToken)`. @@ -1799,18 +1658,15 @@ impl AgentManager { info: libremetaverse::messages::linden::ExperienceInfo, cancellation_token: Option, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateExperienceAsync(LibreMetaverse.Messages.Linden.ExperienceInfo,System.Threading.CancellationToken)", - ) + self.native_update_experience(info, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.UpdateInterests(LibreMetaverse.Avatar.Interests)`. pub fn update_interests( &self, interests: libremetaverse::AvatarInterests, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateInterests(LibreMetaverse.Avatar.Interests)", - ) + self.native_update_interests(interests) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String)`. pub fn update_mute_list_entry_with_mute_type_uuid_string( @@ -1819,9 +1675,7 @@ impl AgentManager { id: libremetaverse_types::UUID, name: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String)", - ) + self.native_update_mute_list_entry(type_, id, name, crate::MuteFlags::DEFAULT) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String,LibreMetaverse.MuteFlags)`. pub fn update_mute_list_entry_with_mute_type_uuid_string_mute_flags( @@ -1831,18 +1685,14 @@ impl AgentManager { name: String, flags: libremetaverse::MuteFlags, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String,LibreMetaverse.MuteFlags)", - ) + self.native_update_mute_list_entry(type_, id, name, flags) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateProfile(LibreMetaverse.Avatar.AvatarProperties)`. pub fn update_profile_with_avatar_properties( &self, profile: libremetaverse::AvatarAvatarProperties, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateProfile(LibreMetaverse.Avatar.AvatarProperties)", - ) + self.native_update_profile(profile) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateProfileAsync(LibreMetaverse.Avatar.AvatarProperties,System.Threading.CancellationToken)`. pub async fn update_profile_with_avatar_properties_cancellation_token( @@ -1850,9 +1700,8 @@ impl AgentManager { profile: libremetaverse::AvatarAvatarProperties, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateProfileAsync(LibreMetaverse.Avatar.AvatarProperties,System.Threading.CancellationToken)", - ) + self.native_update_profile_async(profile, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.UpdateProfileNotes(LibreMetaverse.UUID,System.String)`. pub fn update_profile_notes_with_uuid_string( @@ -1860,9 +1709,7 @@ impl AgentManager { target: libremetaverse_types::UUID, notes: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateProfileNotes(LibreMetaverse.UUID,System.String)", - ) + self.native_update_profile_notes(target, notes) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateProfileNotesAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)`. pub async fn update_profile_notes_with_uuid_string_cancellation_token( @@ -1871,18 +1718,15 @@ impl AgentManager { notes: String, cancellation_token: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateProfileNotesAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)", - ) + self.native_update_profile_notes_async(target, notes, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.UpdateProfileUdp(LibreMetaverse.Avatar.AvatarProperties)`. pub fn update_profile_udp( &self, profile: libremetaverse::AvatarAvatarProperties, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UpdateProfileUdp(LibreMetaverse.Avatar.AvatarProperties)", - ) + self.native_update_profile_udp(profile) } /// C# member: `P:LibreMetaverse.AgentManager.Acceleration`. pub fn acceleration(&self) -> libremetaverse_types::Vector3 { @@ -1894,56 +1738,52 @@ impl AgentManager { ) -> std::sync::RwLock< std::collections::HashMap, > { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ActiveGestures") + self.native_active_gestures() } /// C# member: `P:LibreMetaverse.AgentManager.ActiveGroup`. pub fn active_group(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ActiveGroup") + self.native_active_group() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.ActiveGroup`. pub fn set_active_group(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ActiveGroup") + self.native_set_active_group(value) } /// C# member: `P:LibreMetaverse.AgentManager.ActiveGroupPowers`. pub fn active_group_powers(&self) -> libremetaverse::GroupPowers { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ActiveGroupPowers") + self.native_active_group_powers() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.ActiveGroupPowers`. pub fn set_active_group_powers(&mut self, value: libremetaverse::GroupPowers) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ActiveGroupPowers") + self.native_set_active_group_powers(value) } /// C# member: `P:LibreMetaverse.AgentManager.AgentAccess`. pub fn agent_access(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentAccess") + self.native_agent_access() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentAccess`. pub fn set_agent_access(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentAccess") + self.native_set_agent_access(value) } /// C# member: `P:LibreMetaverse.AgentManager.AgentExperienceList`. pub fn agent_experience_list( &self, ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentExperienceList" - ) + self.native_agent_experience_list() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentExperienceList`. pub fn set_agent_experience_list( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentExperienceList" - ) + self.native_set_agent_experience_list(value) } /// C# member: `P:LibreMetaverse.AgentManager.AgentID`. pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentID") + self.native_agent_id() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentID`. pub fn set_agent_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentID") + self.native_set_agent_id(value) } /// C# member: `P:LibreMetaverse.AgentManager.AngularVelocity`. pub fn angular_velocity(&self) -> libremetaverse_types::Vector3 { @@ -1953,30 +1793,30 @@ impl AgentManager { pub fn avatar_render_info( &self, ) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AvatarRenderInfo") + self.native_avatar_render_info() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.AvatarRenderInfo`. pub fn set_avatar_render_info( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AvatarRenderInfo") + self.native_set_avatar_render_info(value) } /// C# member: `P:LibreMetaverse.AgentManager.Balance`. pub fn balance(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Balance") + self.native_balance() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.Balance`. pub fn set_balance(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Balance") + self.native_set_balance(value) } /// C# member: `P:LibreMetaverse.AgentManager.Benefits`. pub fn benefits(&self) -> libremetaverse::AccountLevelBenefits { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Benefits") + self.native_benefits() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.Benefits`. pub fn set_benefits(&mut self, value: libremetaverse::AccountLevelBenefits) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Benefits") + self.native_set_benefits(value) } /// C# member: `P:LibreMetaverse.AgentManager.CollisionPlane`. pub fn collision_plane(&self) -> libremetaverse_types::Vector4 { @@ -1986,26 +1826,22 @@ impl AgentManager { pub fn experience_preferences( &self, ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.ExperiencePreferences" - ) + self.native_experience_preferences() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.ExperiencePreferences`. pub fn set_experience_preferences( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.ExperiencePreferences" - ) + self.native_set_experience_preferences(value) } /// C# member: `P:LibreMetaverse.AgentManager.FirstName`. pub fn first_name(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.FirstName") + self.native_first_name() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.FirstName`. pub fn set_first_name(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.FirstName") + self.native_set_first_name(value) } /// C# member: `P:LibreMetaverse.AgentManager.GlobalPosition`. pub fn global_position(&self) -> libremetaverse_types::Vector3d { @@ -2033,32 +1869,32 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.HoverHeight`. pub fn hover_height(&self) -> f32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.HoverHeight") + self.native_hover_height() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.HoverHeight`. pub fn set_hover_height(&mut self, value: f32) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.HoverHeight") + self.native_set_hover_height(value) } /// C# member: `P:LibreMetaverse.AgentManager.LastName`. pub fn last_name(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.LastName") + self.native_last_name() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.LastName`. pub fn set_last_name(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.LastName") + self.native_set_last_name(value) } /// C# member: `P:LibreMetaverse.AgentManager.LastNavMeshStatus`. pub fn last_nav_mesh_status( &self, ) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.LastNavMeshStatus") + self.native_last_nav_mesh_status() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.LastNavMeshStatus`. pub fn set_last_nav_mesh_status( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.LastNavMeshStatus") + self.native_set_last_nav_mesh_status(value) } /// C# member: `P:LibreMetaverse.AgentManager.LastPositionUpdate`. pub fn last_position_update(&self) -> std::time::SystemTime { @@ -2072,18 +1908,14 @@ impl AgentManager { pub fn last_region_experiences( &self, ) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.LastRegionExperiences" - ) + self.native_last_region_experiences() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.LastRegionExperiences`. pub fn set_last_region_experiences( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.LastRegionExperiences" - ) + self.native_set_last_region_experiences(value) } /// C# member: `P:LibreMetaverse.AgentManager.LocalID`. pub fn local_id(&self) -> u32 { @@ -2099,20 +1931,20 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.Name`. pub fn name(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Name") + self.native_name() } /// C# member: `P:LibreMetaverse.AgentManager.ProductInfo`. pub fn product_info( &self, ) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ProductInfo") + self.native_product_info() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.ProductInfo`. pub fn set_product_info( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ProductInfo") + self.native_set_product_info(value) } /// C# member: `P:LibreMetaverse.AgentManager.RelativePosition`. pub fn relative_position(&self) -> libremetaverse_types::Vector3 { @@ -2152,11 +1984,11 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.SessionID`. pub fn session_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.SessionID") + self.native_session_id() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.SessionID`. pub fn set_session_id(&mut self, value: libremetaverse_types::UUID) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.SessionID") + self.native_set_session_id(value) } /// C# member: `P:LibreMetaverse.AgentManager.SimPosition`. pub fn sim_position(&self) -> libremetaverse_types::Vector3 { @@ -2194,14 +2026,14 @@ impl AgentManager { pub fn viewer_benefits( &self, ) -> Option { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ViewerBenefits") + self.native_viewer_benefits() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.ViewerBenefits`. pub fn set_viewer_benefits( &mut self, value: Option, ) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.ViewerBenefits") + self.native_set_viewer_benefits(value) } } @@ -2968,23 +2800,9 @@ pub enum AgentManagerCrossingState { } /// C# type: `T:LibreMetaverse.AgentPreferencesEventArgs`. -pub struct AgentPreferencesEventArgs; -impl AgentPreferencesEventArgs { - /// C# member: `M:LibreMetaverse.AgentPreferencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.AgentPreferencesMessage)`. - pub fn new( - preferences: libremetaverse::messages::linden::AgentPreferencesMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentPreferencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.AgentPreferencesMessage)", - ) - } - /// C# member: `P:LibreMetaverse.AgentPreferencesEventArgs.Preferences`. - pub fn preferences(&self) -> libremetaverse::messages::linden::AgentPreferencesMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentPreferencesEventArgs.Preferences" - ) - } -} +/// C# member: `M:LibreMetaverse.AgentPreferencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.AgentPreferencesMessage)`. +/// C# member: `P:LibreMetaverse.AgentPreferencesEventArgs.Preferences`. +pub use crate::agent_messages::AgentPreferencesEventArgs; /// C# type: `T:LibreMetaverse.AgentSettings`. #[derive(Clone, Debug, Eq, PartialEq)] @@ -3645,23 +3463,9 @@ impl Animations { } /// C# type: `T:LibreMetaverse.AnimationsChangedEventArgs`. -pub struct AnimationsChangedEventArgs; -impl AnimationsChangedEventArgs { - /// C# member: `M:LibreMetaverse.AnimationsChangedEventArgs.#ctor(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.Int32})`. - pub fn new( - agent_animations: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AnimationsChangedEventArgs.#ctor(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.Int32})", - ) - } - /// C# member: `P:LibreMetaverse.AnimationsChangedEventArgs.Animations`. - pub fn animations(&self) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AnimationsChangedEventArgs.Animations" - ) - } -} +/// C# member: `M:LibreMetaverse.AnimationsChangedEventArgs.#ctor(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.Int32})`. +/// C# member: `P:LibreMetaverse.AnimationsChangedEventArgs.Animations`. +pub use crate::agent_messages::AnimationsChangedEventArgs; /// C# type: `T:LibreMetaverse.AnimeshSkinning`. pub struct AnimeshSkinning; @@ -5142,69 +4946,71 @@ impl AvatarAvatarProperties { pub fn from_osd( o: libremetaverse_structured_data::OSD, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Avatar.AvatarProperties.FromOSD(LibreMetaverse.StructuredData.OSD)", - ) + crate::agent_messages::avatar_properties_from_osd(o) } /// C# member: `M:LibreMetaverse.Avatar.AvatarProperties.GetOSD`. pub fn get_osd(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Avatar.AvatarProperties.GetOSD") + crate::agent_messages::avatar_properties_get_osd(self) } /// C# member: `P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish`. pub fn allow_publish(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish" - ) + self.flags.0 & crate::ProfileFlags::ALLOW_PUBLISH.0 != 0 } /// Setter for C# member: `P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish`. pub fn set_allow_publish(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish" - ) + if value { + self.flags.0 |= crate::ProfileFlags::ALLOW_PUBLISH.0 + } else { + self.flags.0 &= !crate::ProfileFlags::ALLOW_PUBLISH.0 + } } /// C# member: `P:LibreMetaverse.Avatar.AvatarProperties.Identified`. pub fn identified(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.Identified" - ) + self.flags.0 & crate::ProfileFlags::IDENTIFIED.0 != 0 } /// Setter for C# member: `P:LibreMetaverse.Avatar.AvatarProperties.Identified`. pub fn set_identified(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.Identified" - ) + if value { + self.flags.0 |= crate::ProfileFlags::IDENTIFIED.0 + } else { + self.flags.0 &= !crate::ProfileFlags::IDENTIFIED.0 + } } /// C# member: `P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish`. pub fn mature_publish(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish" - ) + self.flags.0 & crate::ProfileFlags::MATURE_PUBLISH.0 != 0 } /// Setter for C# member: `P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish`. pub fn set_mature_publish(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish" - ) + if value { + self.flags.0 |= crate::ProfileFlags::MATURE_PUBLISH.0 + } else { + self.flags.0 &= !crate::ProfileFlags::MATURE_PUBLISH.0 + } } /// C# member: `P:LibreMetaverse.Avatar.AvatarProperties.Online`. pub fn online(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Avatar.AvatarProperties.Online") + self.flags.0 & crate::ProfileFlags::ONLINE.0 != 0 } /// Setter for C# member: `P:LibreMetaverse.Avatar.AvatarProperties.Online`. pub fn set_online(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Avatar.AvatarProperties.Online") + if value { + self.flags.0 |= crate::ProfileFlags::ONLINE.0 + } else { + self.flags.0 &= !crate::ProfileFlags::ONLINE.0 + } } /// C# member: `P:LibreMetaverse.Avatar.AvatarProperties.Transacted`. pub fn transacted(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.Transacted" - ) + self.flags.0 & crate::ProfileFlags::TRANSACTED.0 != 0 } /// Setter for C# member: `P:LibreMetaverse.Avatar.AvatarProperties.Transacted`. pub fn set_transacted(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Avatar.AvatarProperties.Transacted" - ) + if value { + self.flags.0 |= crate::ProfileFlags::TRANSACTED.0 + } else { + self.flags.0 &= !crate::ProfileFlags::TRANSACTED.0 + } } } @@ -5226,13 +5032,11 @@ impl AvatarInterests { pub fn from_osd( o: libremetaverse_structured_data::OSD, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Avatar.Interests.FromOSD(LibreMetaverse.StructuredData.OSD)", - ) + crate::agent_messages::avatar_interests_from_osd(o) } /// C# member: `M:LibreMetaverse.Avatar.Interests.GetOSD`. pub fn get_osd(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Avatar.Interests.GetOSD") + crate::agent_messages::avatar_interests_get_osd(self) } } @@ -5888,23 +5692,9 @@ impl AvatarPropertiesReplyEventArgs { } /// C# type: `T:LibreMetaverse.AvatarRenderInfoEventArgs`. -pub struct AvatarRenderInfoEventArgs; -impl AvatarRenderInfoEventArgs { - /// C# member: `M:LibreMetaverse.AvatarRenderInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage)`. - pub fn new( - render_info: libremetaverse::messages::linden::AvatarRenderInfoMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarRenderInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage)", - ) - } - /// C# member: `P:LibreMetaverse.AvatarRenderInfoEventArgs.RenderInfo`. - pub fn render_info(&self) -> libremetaverse::messages::linden::AvatarRenderInfoMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarRenderInfoEventArgs.RenderInfo" - ) - } -} +/// C# member: `M:LibreMetaverse.AvatarRenderInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage)`. +/// C# member: `P:LibreMetaverse.AvatarRenderInfoEventArgs.RenderInfo`. +pub use crate::agent_messages::AvatarRenderInfoEventArgs; /// C# type: `T:LibreMetaverse.AvatarSitChangedEventArgs`. pub struct AvatarSitChangedEventArgs; @@ -6174,19 +5964,9 @@ impl BakeType { } /// C# type: `T:LibreMetaverse.BalanceEventArgs`. -pub struct BalanceEventArgs; -impl BalanceEventArgs { - /// C# member: `M:LibreMetaverse.BalanceEventArgs.#ctor(System.Int32)`. - pub fn new(balance: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.BalanceEventArgs.#ctor(System.Int32)", - ) - } - /// C# member: `P:LibreMetaverse.BalanceEventArgs.Balance`. - pub fn balance(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.BalanceEventArgs.Balance") - } -} +/// C# member: `M:LibreMetaverse.BalanceEventArgs.#ctor(System.Int32)`. +/// C# member: `P:LibreMetaverse.BalanceEventArgs.Balance`. +pub use crate::agent_messages::BalanceEventArgs; /// C# type: `T:LibreMetaverse.BannedAgentsEventArgs`. pub struct BannedAgentsEventArgs; @@ -6564,152 +6344,41 @@ pub enum ChatAudibleLevel { } /// C# type: `T:LibreMetaverse.ChatEventArgs`. -pub struct ChatEventArgs; -impl ChatEventArgs { - /// C# member: `M:LibreMetaverse.ChatEventArgs.#ctor(LibreMetaverse.Simulator,System.String,LibreMetaverse.ChatAudibleLevel,LibreMetaverse.ChatType,LibreMetaverse.ChatSourceType,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3)`. - pub fn new( - simulator: libremetaverse::Simulator, - message: String, - audible: libremetaverse::ChatAudibleLevel, - type_: libremetaverse::ChatType, - source_type: libremetaverse::ChatSourceType, - from_name: String, - source_id: libremetaverse_types::UUID, - ownerid: libremetaverse_types::UUID, - position: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ChatEventArgs.#ctor(LibreMetaverse.Simulator,System.String,LibreMetaverse.ChatAudibleLevel,LibreMetaverse.ChatType,LibreMetaverse.ChatSourceType,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.ChatEventArgs.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.ChatEventArgs.ToString") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.AudibleLevel`. - pub fn audible_level(&self) -> libremetaverse::ChatAudibleLevel { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.AudibleLevel") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.FromName`. - pub fn from_name(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.FromName") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.Message`. - pub fn message(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.Message") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.OwnerID`. - pub fn owner_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.OwnerID") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.Position`. - pub fn position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.Position") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.Simulator`. - pub fn simulator(&self) -> libremetaverse::Simulator { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.Simulator") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.SourceID`. - pub fn source_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.SourceID") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.SourceType`. - pub fn source_type(&self) -> libremetaverse::ChatSourceType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.SourceType") - } - /// C# member: `P:LibreMetaverse.ChatEventArgs.Type`. - pub fn type_(&self) -> libremetaverse::ChatType { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.ChatEventArgs.Type") - } -} +/// C# member: `M:LibreMetaverse.ChatEventArgs.#ctor(LibreMetaverse.Simulator,System.String,LibreMetaverse.ChatAudibleLevel,LibreMetaverse.ChatType,LibreMetaverse.ChatSourceType,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.ChatEventArgs.ToString`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.AudibleLevel`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.FromName`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.Message`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.OwnerID`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.Position`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.Simulator`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.SourceID`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.SourceType`. +/// C# member: `P:LibreMetaverse.ChatEventArgs.Type`. +pub use crate::agent_messages::ChatEventArgs; /// C# type: `T:LibreMetaverse.ChatSessionMember`. -pub struct ChatSessionMember { - /// C# member: `F:LibreMetaverse.ChatSessionMember.AvatarKey`. - pub avatar_key: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.ChatSessionMember.CanVoiceChat`. - pub can_voice_chat: bool, - /// C# member: `F:LibreMetaverse.ChatSessionMember.IsModerator`. - pub is_moderator: bool, - /// C# member: `F:LibreMetaverse.ChatSessionMember.MuteText`. - pub mute_text: bool, - /// C# member: `F:LibreMetaverse.ChatSessionMember.MuteVoice`. - pub mute_voice: bool, -} -impl ChatSessionMember { - /// C# member: `M:LibreMetaverse.ChatSessionMember.Equals(LibreMetaverse.ChatSessionMember)`. - pub fn equals_with_chat_session_member( - &self, - other: libremetaverse::ChatSessionMember, - ) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.ChatSessionMember.Equals(LibreMetaverse.ChatSessionMember)" - ) - } - /// C# member: `M:LibreMetaverse.ChatSessionMember.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.ChatSessionMember.Equals(System.Object)" - ) - } - /// C# member: `M:LibreMetaverse.ChatSessionMember.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.ChatSessionMember.GetHashCode") - } -} +/// C# member: `F:LibreMetaverse.ChatSessionMember.AvatarKey`. +/// C# member: `F:LibreMetaverse.ChatSessionMember.CanVoiceChat`. +/// C# member: `F:LibreMetaverse.ChatSessionMember.IsModerator`. +/// C# member: `F:LibreMetaverse.ChatSessionMember.MuteText`. +/// C# member: `F:LibreMetaverse.ChatSessionMember.MuteVoice`. +/// C# member: `M:LibreMetaverse.ChatSessionMember.Equals(LibreMetaverse.ChatSessionMember)`. +/// C# member: `M:LibreMetaverse.ChatSessionMember.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.ChatSessionMember.GetHashCode`. +pub use crate::agent_messages::ChatSessionMember; /// C# type: `T:LibreMetaverse.ChatSessionMemberAddedEventArgs`. -pub struct ChatSessionMemberAddedEventArgs; -impl ChatSessionMemberAddedEventArgs { - /// C# member: `M:LibreMetaverse.ChatSessionMemberAddedEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn new( - session_id: libremetaverse_types::UUID, - agent_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ChatSessionMemberAddedEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.ChatSessionMemberAddedEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ChatSessionMemberAddedEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.ChatSessionMemberAddedEventArgs.SessionID`. - pub fn session_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ChatSessionMemberAddedEventArgs.SessionID" - ) - } -} +/// C# member: `M:LibreMetaverse.ChatSessionMemberAddedEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.ChatSessionMemberAddedEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.ChatSessionMemberAddedEventArgs.SessionID`. +pub use crate::agent_messages::ChatSessionMemberAddedEventArgs; /// C# type: `T:LibreMetaverse.ChatSessionMemberLeftEventArgs`. -pub struct ChatSessionMemberLeftEventArgs; -impl ChatSessionMemberLeftEventArgs { - /// C# member: `M:LibreMetaverse.ChatSessionMemberLeftEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn new( - session_id: libremetaverse_types::UUID, - agent_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ChatSessionMemberLeftEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.ChatSessionMemberLeftEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ChatSessionMemberLeftEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.ChatSessionMemberLeftEventArgs.SessionID`. - pub fn session_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ChatSessionMemberLeftEventArgs.SessionID" - ) - } -} +/// C# member: `M:LibreMetaverse.ChatSessionMemberLeftEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.ChatSessionMemberLeftEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.ChatSessionMemberLeftEventArgs.SessionID`. +pub use crate::agent_messages::ChatSessionMemberLeftEventArgs; /// C# type: `T:LibreMetaverse.ChatSourceType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -9464,42 +9133,14 @@ impl EventSubscription { } /// C# type: `T:LibreMetaverse.ExperienceInfoEventArgs`. -pub struct ExperienceInfoEventArgs; -impl ExperienceInfoEventArgs { - /// C# member: `M:LibreMetaverse.ExperienceInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperienceInfoMessage)`. - pub fn new( - experience_info: libremetaverse::messages::linden::ExperienceInfoMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ExperienceInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperienceInfoMessage)", - ) - } - /// C# member: `P:LibreMetaverse.ExperienceInfoEventArgs.ExperienceInfo`. - pub fn experience_info(&self) -> libremetaverse::messages::linden::ExperienceInfoMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ExperienceInfoEventArgs.ExperienceInfo" - ) - } -} +/// C# member: `M:LibreMetaverse.ExperienceInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperienceInfoMessage)`. +/// C# member: `P:LibreMetaverse.ExperienceInfoEventArgs.ExperienceInfo`. +pub use crate::agent_messages::ExperienceInfoEventArgs; /// C# type: `T:LibreMetaverse.ExperiencePreferencesEventArgs`. -pub struct ExperiencePreferencesEventArgs; -impl ExperiencePreferencesEventArgs { - /// C# member: `M:LibreMetaverse.ExperiencePreferencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage)`. - pub fn new( - preferences: libremetaverse::messages::linden::ExperiencePreferencesMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ExperiencePreferencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage)", - ) - } - /// C# member: `P:LibreMetaverse.ExperiencePreferencesEventArgs.Preferences`. - pub fn preferences(&self) -> libremetaverse::messages::linden::ExperiencePreferencesMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ExperiencePreferencesEventArgs.Preferences" - ) - } -} +/// C# member: `M:LibreMetaverse.ExperiencePreferencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage)`. +/// C# member: `P:LibreMetaverse.ExperiencePreferencesEventArgs.Preferences`. +pub use crate::agent_messages::ExperiencePreferencesEventArgs; /// C# type: `T:LibreMetaverse.FieldType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -10483,7 +10124,8 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Self`. pub fn self_(&mut self) -> &mut libremetaverse::AgentManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Self") + /* native client-core implementation */ + self.native_self() } /// C# member: `P:LibreMetaverse.GridClient.Settings`. pub fn settings(&mut self) -> &mut libremetaverse::Settings { @@ -11069,44 +10711,12 @@ pub enum GroupBanAction { } /// C# type: `T:LibreMetaverse.GroupChatJoinedEventArgs`. -pub struct GroupChatJoinedEventArgs; -impl GroupChatJoinedEventArgs { - /// C# member: `M:LibreMetaverse.GroupChatJoinedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean)`. - pub fn new( - group_chat_session_id: libremetaverse_types::UUID, - session_name: String, - tmp_session_id: libremetaverse_types::UUID, - success: bool, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupChatJoinedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.SessionID`. - pub fn session_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupChatJoinedEventArgs.SessionID" - ) - } - /// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.SessionName`. - pub fn session_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupChatJoinedEventArgs.SessionName" - ) - } - /// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupChatJoinedEventArgs.Success" - ) - } - /// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.TmpSessionID`. - pub fn tmp_session_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupChatJoinedEventArgs.TmpSessionID" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupChatJoinedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.SessionID`. +/// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.SessionName`. +/// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.Success`. +/// C# member: `P:LibreMetaverse.GroupChatJoinedEventArgs.TmpSessionID`. +pub use crate::agent_messages::GroupChatJoinedEventArgs; /// C# type: `T:LibreMetaverse.GroupCreatedReplyEventArgs`. pub struct GroupCreatedReplyEventArgs; @@ -12669,40 +12279,21 @@ impl InitiateDownloadEventArgs { } /// C# type: `T:LibreMetaverse.InstantMessage`. -pub struct InstantMessage { - /// C# member: `F:LibreMetaverse.InstantMessage.BinaryBucket`. - pub binary_bucket: Vec, - /// C# member: `F:LibreMetaverse.InstantMessage.Dialog`. - pub dialog: libremetaverse::InstantMessageDialog, - /// C# member: `F:LibreMetaverse.InstantMessage.FromAgentID`. - pub from_agent_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.InstantMessage.FromAgentName`. - pub from_agent_name: String, - /// C# member: `F:LibreMetaverse.InstantMessage.GroupIM`. - pub group_im: bool, - /// C# member: `F:LibreMetaverse.InstantMessage.IMSessionID`. - pub im_session_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.InstantMessage.Message`. - pub message: String, - /// C# member: `F:LibreMetaverse.InstantMessage.Offline`. - pub offline: libremetaverse::InstantMessageOnline, - /// C# member: `F:LibreMetaverse.InstantMessage.ParentEstateID`. - pub parent_estate_id: u32, - /// C# member: `F:LibreMetaverse.InstantMessage.Position`. - pub position: libremetaverse_types::Vector3, - /// C# member: `F:LibreMetaverse.InstantMessage.RegionID`. - pub region_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.InstantMessage.Timestamp`. - pub timestamp: std::time::SystemTime, - /// C# member: `F:LibreMetaverse.InstantMessage.ToAgentID`. - pub to_agent_id: libremetaverse_types::UUID, -} -impl InstantMessage { - /// C# member: `M:LibreMetaverse.InstantMessage.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.InstantMessage.ToString") - } -} +/// C# member: `F:LibreMetaverse.InstantMessage.BinaryBucket`. +/// C# member: `F:LibreMetaverse.InstantMessage.Dialog`. +/// C# member: `F:LibreMetaverse.InstantMessage.FromAgentID`. +/// C# member: `F:LibreMetaverse.InstantMessage.FromAgentName`. +/// C# member: `F:LibreMetaverse.InstantMessage.GroupIM`. +/// C# member: `F:LibreMetaverse.InstantMessage.IMSessionID`. +/// C# member: `F:LibreMetaverse.InstantMessage.Message`. +/// C# member: `F:LibreMetaverse.InstantMessage.Offline`. +/// C# member: `F:LibreMetaverse.InstantMessage.ParentEstateID`. +/// C# member: `F:LibreMetaverse.InstantMessage.Position`. +/// C# member: `F:LibreMetaverse.InstantMessage.RegionID`. +/// C# member: `F:LibreMetaverse.InstantMessage.Timestamp`. +/// C# member: `F:LibreMetaverse.InstantMessage.ToAgentID`. +/// C# member: `M:LibreMetaverse.InstantMessage.ToString`. +pub use crate::agent_manager::InstantMessage; /// C# type: `T:LibreMetaverse.InstantMessageDialog`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -12791,28 +12382,10 @@ pub enum InstantMessageDialog { } /// C# type: `T:LibreMetaverse.InstantMessageEventArgs`. -pub struct InstantMessageEventArgs; -impl InstantMessageEventArgs { - /// C# member: `M:LibreMetaverse.InstantMessageEventArgs.#ctor(LibreMetaverse.InstantMessage,LibreMetaverse.Simulator)`. - pub fn new( - im: libremetaverse::InstantMessage, - simulator: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.InstantMessageEventArgs.#ctor(LibreMetaverse.InstantMessage,LibreMetaverse.Simulator)", - ) - } - /// C# member: `P:LibreMetaverse.InstantMessageEventArgs.IM`. - pub fn im(&self) -> libremetaverse::InstantMessage { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.InstantMessageEventArgs.IM") - } - /// C# member: `P:LibreMetaverse.InstantMessageEventArgs.Simulator`. - pub fn simulator(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.InstantMessageEventArgs.Simulator" - ) - } -} +/// C# member: `M:LibreMetaverse.InstantMessageEventArgs.#ctor(LibreMetaverse.InstantMessage,LibreMetaverse.Simulator)`. +/// C# member: `P:LibreMetaverse.InstantMessageEventArgs.IM`. +/// C# member: `P:LibreMetaverse.InstantMessageEventArgs.Simulator`. +pub use crate::agent_manager::InstantMessageEventArgs; /// C# type: `T:LibreMetaverse.InstantMessageOnline`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -17240,65 +16813,15 @@ impl MediaPermission { } /// C# type: `T:LibreMetaverse.MoneyBalanceReplyEventArgs`. -pub struct MoneyBalanceReplyEventArgs; -impl MoneyBalanceReplyEventArgs { - /// C# member: `M:LibreMetaverse.MoneyBalanceReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.Int32,System.Int32,System.Int32,System.String,LibreMetaverse.TransactionInfo)`. - pub fn new( - transaction_id: libremetaverse_types::UUID, - transaction_success: bool, - balance: i32, - meters_credit: i32, - meters_committed: i32, - description: String, - transaction_info: libremetaverse::TransactionInfo, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.MoneyBalanceReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.Int32,System.Int32,System.Int32,System.String,LibreMetaverse.TransactionInfo)", - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.Balance`. - pub fn balance(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.Balance" - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.Description`. - pub fn description(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.Description" - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.MetersCommitted`. - pub fn meters_committed(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.MetersCommitted" - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.MetersCredit`. - pub fn meters_credit(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.MetersCredit" - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.Success" - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.TransactionID`. - pub fn transaction_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.TransactionID" - ) - } - /// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.TransactionInfo`. - pub fn transaction_info(&self) -> libremetaverse::TransactionInfo { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.MoneyBalanceReplyEventArgs.TransactionInfo" - ) - } -} +/// C# member: `M:LibreMetaverse.MoneyBalanceReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.Int32,System.Int32,System.Int32,System.String,LibreMetaverse.TransactionInfo)`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.Balance`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.Description`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.MetersCommitted`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.MetersCredit`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.Success`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.TransactionID`. +/// C# member: `P:LibreMetaverse.MoneyBalanceReplyEventArgs.TransactionInfo`. +pub use crate::agent_messages::MoneyBalanceReplyEventArgs; /// C# type: `T:LibreMetaverse.MoneyTransactionType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -17568,30 +17091,10 @@ pub enum NameValueValueType { } /// C# type: `T:LibreMetaverse.NavMeshStatusUpdateEventArgs`. -pub struct NavMeshStatusUpdateEventArgs; -impl NavMeshStatusUpdateEventArgs { - /// C# member: `M:LibreMetaverse.NavMeshStatusUpdateEventArgs.#ctor(LibreMetaverse.Messages.Linden.NavMeshStatusUpdateMessage,LibreMetaverse.Simulator)`. - pub fn new( - message: libremetaverse::messages::linden::NavMeshStatusUpdateMessage, - simulator: libremetaverse::Simulator, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.NavMeshStatusUpdateEventArgs.#ctor(LibreMetaverse.Messages.Linden.NavMeshStatusUpdateMessage,LibreMetaverse.Simulator)", - ) - } - /// C# member: `P:LibreMetaverse.NavMeshStatusUpdateEventArgs.Message`. - pub fn message(&self) -> libremetaverse::messages::linden::NavMeshStatusUpdateMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.NavMeshStatusUpdateEventArgs.Message" - ) - } - /// C# member: `P:LibreMetaverse.NavMeshStatusUpdateEventArgs.Simulator`. - pub fn simulator(&self) -> libremetaverse::Simulator { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.NavMeshStatusUpdateEventArgs.Simulator" - ) - } -} +/// C# member: `M:LibreMetaverse.NavMeshStatusUpdateEventArgs.#ctor(LibreMetaverse.Messages.Linden.NavMeshStatusUpdateMessage,LibreMetaverse.Simulator)`. +/// C# member: `P:LibreMetaverse.NavMeshStatusUpdateEventArgs.Message`. +/// C# member: `P:LibreMetaverse.NavMeshStatusUpdateEventArgs.Simulator`. +pub use crate::agent_messages::NavMeshStatusUpdateEventArgs; /// C# type: `T:LibreMetaverse.NetworkManager`. pub use crate::network_manager::NetworkManager; @@ -22647,23 +22150,9 @@ impl PrimitiveTextureEntryFace { } /// C# type: `T:LibreMetaverse.ProductInfoEventArgs`. -pub struct ProductInfoEventArgs; -impl ProductInfoEventArgs { - /// C# member: `M:LibreMetaverse.ProductInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.ProductInfoRequestMessage)`. - pub fn new( - product_info: libremetaverse::messages::linden::ProductInfoRequestMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ProductInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.ProductInfoRequestMessage)", - ) - } - /// C# member: `P:LibreMetaverse.ProductInfoEventArgs.ProductInfo`. - pub fn product_info(&self) -> libremetaverse::messages::linden::ProductInfoRequestMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ProductInfoEventArgs.ProductInfo" - ) - } -} +/// C# member: `M:LibreMetaverse.ProductInfoEventArgs.#ctor(LibreMetaverse.Messages.Linden.ProductInfoRequestMessage)`. +/// C# member: `P:LibreMetaverse.ProductInfoEventArgs.ProductInfo`. +pub use crate::agent_messages::ProductInfoEventArgs; /// C# type: `T:LibreMetaverse.ProfileFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -22871,23 +22360,9 @@ impl RegionEnvironmentEventArgs { } /// C# type: `T:LibreMetaverse.RegionExperiencesEventArgs`. -pub struct RegionExperiencesEventArgs; -impl RegionExperiencesEventArgs { - /// C# member: `M:LibreMetaverse.RegionExperiencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.RegionExperiencesMessage)`. - pub fn new( - region_experiences: libremetaverse::messages::linden::RegionExperiencesMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.RegionExperiencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.RegionExperiencesMessage)", - ) - } - /// C# member: `P:LibreMetaverse.RegionExperiencesEventArgs.RegionExperiences`. - pub fn region_experiences(&self) -> libremetaverse::messages::linden::RegionExperiencesMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionExperiencesEventArgs.RegionExperiences" - ) - } -} +/// C# member: `M:LibreMetaverse.RegionExperiencesEventArgs.#ctor(LibreMetaverse.Messages.Linden.RegionExperiencesMessage)`. +/// C# member: `P:LibreMetaverse.RegionExperiencesEventArgs.RegionExperiences`. +pub use crate::agent_messages::RegionExperiencesEventArgs; /// C# type: `T:LibreMetaverse.RegionFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -23462,37 +22937,11 @@ impl ScriptSensorTypeFlags { } /// C# type: `T:LibreMetaverse.SetDisplayNameReplyEventArgs`. -pub struct SetDisplayNameReplyEventArgs; -impl SetDisplayNameReplyEventArgs { - /// C# member: `M:LibreMetaverse.SetDisplayNameReplyEventArgs.#ctor(System.Int32,System.String,LibreMetaverse.AgentDisplayName)`. - pub fn new( - status: i32, - reason: String, - display_name: libremetaverse::AgentDisplayName, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.SetDisplayNameReplyEventArgs.#ctor(System.Int32,System.String,LibreMetaverse.AgentDisplayName)", - ) - } - /// C# member: `P:LibreMetaverse.SetDisplayNameReplyEventArgs.DisplayName`. - pub fn display_name(&self) -> libremetaverse::AgentDisplayName { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.SetDisplayNameReplyEventArgs.DisplayName" - ) - } - /// C# member: `P:LibreMetaverse.SetDisplayNameReplyEventArgs.Reason`. - pub fn reason(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.SetDisplayNameReplyEventArgs.Reason" - ) - } - /// C# member: `P:LibreMetaverse.SetDisplayNameReplyEventArgs.Status`. - pub fn status(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.SetDisplayNameReplyEventArgs.Status" - ) - } -} +/// C# member: `M:LibreMetaverse.SetDisplayNameReplyEventArgs.#ctor(System.Int32,System.String,LibreMetaverse.AgentDisplayName)`. +/// C# member: `P:LibreMetaverse.SetDisplayNameReplyEventArgs.DisplayName`. +/// C# member: `P:LibreMetaverse.SetDisplayNameReplyEventArgs.Reason`. +/// C# member: `P:LibreMetaverse.SetDisplayNameReplyEventArgs.Status`. +pub use crate::agent_messages::SetDisplayNameReplyEventArgs; /// C# type: `T:LibreMetaverse.Settings`. pub use crate::client_core::Settings; @@ -25454,7 +24903,15 @@ pub struct TransactionInfo { impl TransactionInfo { /// C# member: `M:LibreMetaverse.TransactionInfo.#ctor`. pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.TransactionInfo.#ctor") + Ok(crate::TransactionInfo { + amount: 0, + dest_id: libremetaverse_types::UUID::zero(), + is_dest_group: false, + is_source_group: false, + item_description: String::new(), + source_id: libremetaverse_types::UUID::zero(), + transaction_type: 0, + }) } } @@ -25652,23 +25109,9 @@ impl UpdateType { } /// C# type: `T:LibreMetaverse.ViewerBenefitsEventArgs`. -pub struct ViewerBenefitsEventArgs; -impl ViewerBenefitsEventArgs { - /// C# member: `M:LibreMetaverse.ViewerBenefitsEventArgs.#ctor(LibreMetaverse.Messages.Linden.ViewerBenefitsMessage)`. - pub fn new( - benefits: libremetaverse::messages::linden::ViewerBenefitsMessage, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.ViewerBenefitsEventArgs.#ctor(LibreMetaverse.Messages.Linden.ViewerBenefitsMessage)", - ) - } - /// C# member: `P:LibreMetaverse.ViewerBenefitsEventArgs.Benefits`. - pub fn benefits(&self) -> libremetaverse::messages::linden::ViewerBenefitsMessage { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.ViewerBenefitsEventArgs.Benefits" - ) - } -} +/// C# member: `M:LibreMetaverse.ViewerBenefitsEventArgs.#ctor(LibreMetaverse.Messages.Linden.ViewerBenefitsMessage)`. +/// C# member: `P:LibreMetaverse.ViewerBenefitsEventArgs.Benefits`. +pub use crate::agent_messages::ViewerBenefitsEventArgs; /// C# type: `T:LibreMetaverse.ViewerEffectEventArgs`. pub struct ViewerEffectEventArgs; @@ -26897,47 +26340,17 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.AssetGesture`. - pub struct AssetGesture { - /// C# member: `F:LibreMetaverse.Assets.AssetGesture.ReplaceWith`. - pub replace_with: String, - /// C# member: `F:LibreMetaverse.Assets.AssetGesture.Sequence`. - pub sequence: Vec, - /// C# member: `F:LibreMetaverse.Assets.AssetGesture.Trigger`. - pub trigger: String, - /// C# member: `F:LibreMetaverse.Assets.AssetGesture.TriggerKey`. - pub trigger_key: u8, - /// C# member: `F:LibreMetaverse.Assets.AssetGesture.TriggerKeyMask`. - pub trigger_key_mask: u32, - } - impl AssetGesture { - /// C# member: `M:LibreMetaverse.Assets.AssetGesture.#ctor`. - pub fn new_with_constructor() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetGesture.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.AssetGesture.#ctor(LibreMetaverse.UUID,System.Byte[])`. - pub fn new_with_uuid_bytes( - asset_id: libremetaverse_types::UUID, - asset_data: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.AssetGesture.#ctor(LibreMetaverse.UUID,System.Byte[])", - ) - } - /// C# member: `M:LibreMetaverse.Assets.AssetGesture.Decode`. - pub fn decode(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetGesture.Decode") - } - /// C# member: `M:LibreMetaverse.Assets.AssetGesture.Encode`. - pub fn encode(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.AssetGesture.Encode") - } - /// C# member: `P:LibreMetaverse.Assets.AssetGesture.AssetType`. - pub fn asset_type(&self) -> libremetaverse_types::AssetType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.AssetGesture.AssetType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.AssetGesture.ReplaceWith`. + /// C# member: `F:LibreMetaverse.Assets.AssetGesture.Sequence`. + /// C# member: `F:LibreMetaverse.Assets.AssetGesture.Trigger`. + /// C# member: `F:LibreMetaverse.Assets.AssetGesture.TriggerKey`. + /// C# member: `F:LibreMetaverse.Assets.AssetGesture.TriggerKeyMask`. + /// C# member: `M:LibreMetaverse.Assets.AssetGesture.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.AssetGesture.#ctor(LibreMetaverse.UUID,System.Byte[])`. + /// C# member: `M:LibreMetaverse.Assets.AssetGesture.Decode`. + /// C# member: `M:LibreMetaverse.Assets.AssetGesture.Encode`. + /// C# member: `P:LibreMetaverse.Assets.AssetGesture.AssetType`. + pub use crate::gesture::AssetGesture; /// C# type: `T:LibreMetaverse.Assets.AssetLandmark`. pub struct AssetLandmark { @@ -27758,116 +27171,38 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.GestureStep`. - pub struct GestureStep; - impl GestureStep { - /// C# member: `P:LibreMetaverse.Assets.GestureStep.GestureStepType`. - pub fn gesture_step_type(&self) -> libremetaverse::assets::GestureStepType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GestureStep.GestureStepType" - ) - } - } + /// C# member: `P:LibreMetaverse.Assets.GestureStep.GestureStepType`. + pub use crate::gesture::GestureStep; /// C# type: `T:LibreMetaverse.Assets.GestureStepAnimation`. - pub struct GestureStepAnimation { - /// C# member: `F:LibreMetaverse.Assets.GestureStepAnimation.AnimationStart`. - pub animation_start: bool, - /// C# member: `F:LibreMetaverse.Assets.GestureStepAnimation.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.GestureStepAnimation.Name`. - pub name: String, - } - impl GestureStepAnimation { - /// C# member: `M:LibreMetaverse.Assets.GestureStepAnimation.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Assets.GestureStepAnimation.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Assets.GestureStepAnimation.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GestureStepAnimation.ToString" - ) - } - /// C# member: `P:LibreMetaverse.Assets.GestureStepAnimation.GestureStepType`. - pub fn gesture_step_type(&self) -> libremetaverse::assets::GestureStepType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GestureStepAnimation.GestureStepType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.GestureStepAnimation.AnimationStart`. + /// C# member: `F:LibreMetaverse.Assets.GestureStepAnimation.ID`. + /// C# member: `F:LibreMetaverse.Assets.GestureStepAnimation.Name`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepAnimation.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepAnimation.ToString`. + /// C# member: `P:LibreMetaverse.Assets.GestureStepAnimation.GestureStepType`. + pub use crate::gesture::GestureStepAnimation; /// C# type: `T:LibreMetaverse.Assets.GestureStepChat`. - pub struct GestureStepChat { - /// C# member: `F:LibreMetaverse.Assets.GestureStepChat.Text`. - pub text: String, - } - impl GestureStepChat { - /// C# member: `M:LibreMetaverse.Assets.GestureStepChat.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.GestureStepChat.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.GestureStepChat.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GestureStepChat.ToString" - ) - } - /// C# member: `P:LibreMetaverse.Assets.GestureStepChat.GestureStepType`. - pub fn gesture_step_type(&self) -> libremetaverse::assets::GestureStepType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GestureStepChat.GestureStepType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.GestureStepChat.Text`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepChat.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepChat.ToString`. + /// C# member: `P:LibreMetaverse.Assets.GestureStepChat.GestureStepType`. + pub use crate::gesture::GestureStepChat; /// C# type: `T:LibreMetaverse.Assets.GestureStepEOF`. - pub struct GestureStepEOF; - impl GestureStepEOF { - /// C# member: `M:LibreMetaverse.Assets.GestureStepEOF.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.GestureStepEOF.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.GestureStepEOF.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GestureStepEOF.ToString" - ) - } - /// C# member: `P:LibreMetaverse.Assets.GestureStepEOF.GestureStepType`. - pub fn gesture_step_type(&self) -> libremetaverse::assets::GestureStepType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GestureStepEOF.GestureStepType" - ) - } - } + /// C# member: `M:LibreMetaverse.Assets.GestureStepEOF.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepEOF.ToString`. + /// C# member: `P:LibreMetaverse.Assets.GestureStepEOF.GestureStepType`. + pub use crate::gesture::GestureStepEOF; /// C# type: `T:LibreMetaverse.Assets.GestureStepSound`. - pub struct GestureStepSound { - /// C# member: `F:LibreMetaverse.Assets.GestureStepSound.ID`. - pub id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Assets.GestureStepSound.Name`. - pub name: String, - } - impl GestureStepSound { - /// C# member: `M:LibreMetaverse.Assets.GestureStepSound.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.GestureStepSound.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.GestureStepSound.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GestureStepSound.ToString" - ) - } - /// C# member: `P:LibreMetaverse.Assets.GestureStepSound.GestureStepType`. - pub fn gesture_step_type(&self) -> libremetaverse::assets::GestureStepType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GestureStepSound.GestureStepType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.GestureStepSound.ID`. + /// C# member: `F:LibreMetaverse.Assets.GestureStepSound.Name`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepSound.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepSound.ToString`. + /// C# member: `P:LibreMetaverse.Assets.GestureStepSound.GestureStepType`. + pub use crate::gesture::GestureStepSound; /// C# type: `T:LibreMetaverse.Assets.GestureStepType`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -27886,32 +27221,13 @@ pub mod assets { } /// C# type: `T:LibreMetaverse.Assets.GestureStepWait`. - pub struct GestureStepWait { - /// C# member: `F:LibreMetaverse.Assets.GestureStepWait.WaitForAnimation`. - pub wait_for_animation: bool, - /// C# member: `F:LibreMetaverse.Assets.GestureStepWait.WaitForTime`. - pub wait_for_time: bool, - /// C# member: `F:LibreMetaverse.Assets.GestureStepWait.WaitTime`. - pub wait_time: f32, - } - impl GestureStepWait { - /// C# member: `M:LibreMetaverse.Assets.GestureStepWait.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.Assets.GestureStepWait.#ctor") - } - /// C# member: `M:LibreMetaverse.Assets.GestureStepWait.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Assets.GestureStepWait.ToString" - ) - } - /// C# member: `P:LibreMetaverse.Assets.GestureStepWait.GestureStepType`. - pub fn gesture_step_type(&self) -> libremetaverse::assets::GestureStepType { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.Assets.GestureStepWait.GestureStepType" - ) - } - } + /// C# member: `F:LibreMetaverse.Assets.GestureStepWait.WaitForAnimation`. + /// C# member: `F:LibreMetaverse.Assets.GestureStepWait.WaitForTime`. + /// C# member: `F:LibreMetaverse.Assets.GestureStepWait.WaitTime`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepWait.#ctor`. + /// C# member: `M:LibreMetaverse.Assets.GestureStepWait.ToString`. + /// C# member: `P:LibreMetaverse.Assets.GestureStepWait.GestureStepType`. + pub use crate::gesture::GestureStepWait; /// C# type: `T:LibreMetaverse.Assets.GltfAlphaMode`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -62370,38 +61686,12 @@ pub mod messages { } /// C# type: `T:LibreMetaverse.Messages.Linden.AgentPreferencesMessage`. - pub struct AgentPreferencesMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.HoverHeight`. - pub hover_height: f32, - /// C# member: `F:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.RawData`. - pub raw_data: libremetaverse_structured_data::OSDMap, - } - impl AgentPreferencesMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for AgentPreferencesMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.HoverHeight`. + /// C# member: `F:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.RawData`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AgentPreferencesMessage.Serialize`. + pub use crate::agent_messages::AgentPreferencesMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.AgentProfileMessage`. pub struct AgentProfileMessage { @@ -62641,59 +61931,19 @@ pub mod messages { pub use crate::message_decoder::AttachmentResourcesMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage`. - pub struct AvatarRenderInfoMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Agents`. - pub agents: std::collections::HashMap< - libremetaverse_types::UUID, - libremetaverse::messages::linden::AvatarRenderInfoMessageAvatarInfo, - >, - /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.OverLimit`. - pub over_limit: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.ReportingLimit`. - pub reporting_limit: i32, - } - impl AvatarRenderInfoMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for AvatarRenderInfoMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Agents`. + /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.OverLimit`. + /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.ReportingLimit`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.Serialize`. + pub use crate::agent_messages::AvatarRenderInfoMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo`. - pub struct AvatarRenderInfoMessageAvatarInfo { - /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.TooComplex`. - pub too_complex: bool, - /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.Weight`. - pub weight: i32, - } - impl AvatarRenderInfoMessageAvatarInfo { - /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.#ctor", - ) - } - } + /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.TooComplex`. + /// C# member: `F:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.Weight`. + /// C# member: `M:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo.#ctor`. + pub use crate::agent_messages::AvatarRenderInfoMessageAvatarInfo; /// C# type: `T:LibreMetaverse.Messages.Linden.BaseResourcesInfo`. pub struct BaseResourcesInfo { @@ -63666,157 +62916,45 @@ pub mod messages { } /// C# type: `T:LibreMetaverse.Messages.Linden.ExperienceInfo`. - pub struct ExperienceInfo { - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.AgentID`. - pub agent_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Description`. - pub description: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.ExperienceID`. - pub experience_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.ExtendedMetadata`. - pub extended_metadata: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Flags`. - pub flags: libremetaverse::messages::linden::ExperienceFlags, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.GroupID`. - pub group_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.ImageID`. - pub image_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Marketplace`. - pub marketplace: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Maturity`. - pub maturity: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.PublicID`. - pub public_id: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Quota`. - pub quota: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.SLURL`. - pub slurl: String, - } - impl ExperienceInfo { - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfo.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceInfo.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfo.FromOSD(LibreMetaverse.StructuredData.OSDMap)`. - pub fn from_osd( - map: libremetaverse_structured_data::OSDMap, - ) -> Result - { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceInfo.FromOSD(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfo.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceInfo.Serialize", - ) - } - } + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.AgentID`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Description`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.ExperienceID`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.ExtendedMetadata`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Flags`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.GroupID`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.ImageID`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Marketplace`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Maturity`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Name`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.PublicID`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.Quota`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfo.SLURL`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfo.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfo.FromOSD(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfo.Serialize`. + pub use crate::agent_messages::ExperienceInfo; /// C# type: `T:LibreMetaverse.Messages.Linden.ExperienceInfoMessage`. - pub struct ExperienceInfoMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Experiences`. - pub experiences: Vec, - } - impl ExperienceInfoMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for ExperienceInfoMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Experiences`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceInfoMessage.Serialize`. + pub use crate::agent_messages::ExperienceInfoMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.ExperienceListMessage`. - pub struct ExperienceListMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceListMessage.ExperienceIDs`. - pub experience_i_ds: Vec, - } - impl ExperienceListMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceListMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceListMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceListMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceListMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceListMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperienceListMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for ExperienceListMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperienceListMessage.ExperienceIDs`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceListMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceListMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperienceListMessage.Serialize`. + pub use crate::agent_messages::ExperienceListMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage`. - pub struct ExperiencePreferencesMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Allowed`. - pub allowed: Vec, - /// C# member: `F:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Blocked`. - pub blocked: Vec, - } - impl ExperiencePreferencesMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for ExperiencePreferencesMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Allowed`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Blocked`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage.Serialize`. + pub use crate::agent_messages::ExperiencePreferencesMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.ExtEnvironmentMessage`. pub struct ExtEnvironmentMessage; @@ -65463,55 +64601,18 @@ pub mod messages { } /// C# type: `T:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage`. - pub struct ProductInfoRequestMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Products`. - pub products: - Vec, - } - impl ProductInfoRequestMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for ProductInfoRequestMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Products`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.Serialize`. + pub use crate::agent_manager::ProductInfoRequestMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo`. - pub struct ProductInfoRequestMessageProductInfo { - /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.Description`. - pub description: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.Name`. - pub name: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.Sku`. - pub sku: String, - } - impl ProductInfoRequestMessageProductInfo { - /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.#ctor", - ) - } - } + /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.Description`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.Name`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.Sku`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo.#ctor`. + pub use crate::agent_manager::ProductInfoRequestMessageProductInfo; /// C# type: `T:LibreMetaverse.Messages.Linden.ProvisionVoiceAccountRequestMessage`. pub struct ProvisionVoiceAccountRequestMessage { @@ -65552,42 +64653,14 @@ pub mod messages { } /// C# type: `T:LibreMetaverse.Messages.Linden.RegionExperiencesMessage`. - pub struct RegionExperiencesMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Allowed`. - pub allowed: Vec, - /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Blocked`. - pub blocked: Vec, - /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Default`. - pub default: libremetaverse_types::UUID, - /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Trusted`. - pub trusted: Vec, - } - impl RegionExperiencesMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for RegionExperiencesMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Allowed`. + /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Blocked`. + /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Default`. + /// C# member: `F:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Trusted`. + /// C# member: `M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.RegionExperiencesMessage.Serialize`. + pub use crate::agent_messages::RegionExperiencesMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.RegionInfoMessage`. pub struct RegionInfoMessage { @@ -66953,74 +66026,26 @@ pub mod messages { } /// C# type: `T:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage`. - pub struct ViewerBenefitsMessage { - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.AccountLevelBenefits`. - pub account_level_benefits: - libremetaverse::messages::linden::ViewerBenefitsMessageBenefitPackage, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.AccountType`. - pub account_type: String, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.PremiumPackages`. - pub premium_packages: std::collections::HashMap< - String, - libremetaverse::messages::linden::ViewerBenefitsMessageBenefitPackage, - >, - } - impl ViewerBenefitsMessage { - /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. - pub fn deserialize( - &mut self, - map: libremetaverse_structured_data::OSDMap, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)", - ) - } - /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.Serialize`. - pub fn serialize( - &self, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.Serialize", - ) - } - } - impl libremetaverse::interfaces::IMessage for ViewerBenefitsMessage {} + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.AccountLevelBenefits`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.AccountType`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.PremiumPackages`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.#ctor`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.Deserialize(LibreMetaverse.StructuredData.OSDMap)`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.Serialize`. + pub use crate::agent_messages::ViewerBenefitsMessage; /// C# type: `T:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage`. - pub struct ViewerBenefitsMessageBenefitPackage { - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.AnimatedObjectLimit`. - pub animated_object_limit: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.AnimationUploadCost`. - pub animation_upload_cost: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.AttachmentLimit`. - pub attachment_limit: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.CreateGroupCost`. - pub create_group_cost: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.GroupMembershipLimit`. - pub group_membership_limit: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.LargeTextureUploadCost`. - pub large_texture_upload_cost: Vec, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.PicksLimit`. - pub picks_limit: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.SoundUploadCost`. - pub sound_upload_cost: i32, - /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.TextureUploadCost`. - pub texture_upload_cost: i32, - } - impl ViewerBenefitsMessageBenefitPackage { - /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.#ctor", - ) - } - } + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.AnimatedObjectLimit`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.AnimationUploadCost`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.AttachmentLimit`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.CreateGroupCost`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.GroupMembershipLimit`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.LargeTextureUploadCost`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.PicksLimit`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.SoundUploadCost`. + /// C# member: `F:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.TextureUploadCost`. + /// C# member: `M:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage.#ctor`. + pub use crate::agent_messages::ViewerBenefitsMessageBenefitPackage; /// C# type: `T:LibreMetaverse.Messages.Linden.ViewerStatsMessage`. pub struct ViewerStatsMessage { diff --git a/crates/libremetaverse/src/gesture.rs b/crates/libremetaverse/src/gesture.rs new file mode 100644 index 0000000..ec5b688 --- /dev/null +++ b/crates/libremetaverse/src/gesture.rs @@ -0,0 +1,375 @@ +//! Native gesture asset parsing and step types. + +#![allow(clippy::inherent_to_string)] // Public names mirror C# ToString mappings. +#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility API. + +use crate::{Error, assets::GestureStepType}; +use libremetaverse_types::{AssetType, UUID}; +use std::fmt::Write as _; + +#[derive(Clone, Debug, PartialEq)] +pub enum GestureStep { + Animation(GestureStepAnimation), + Sound(GestureStepSound), + Chat(GestureStepChat), + Wait(GestureStepWait), + EOF(GestureStepEOF), +} + +impl GestureStep { + #[must_use] + pub const fn gesture_step_type(&self) -> GestureStepType { + match self { + Self::Animation(_) => GestureStepType::Animation, + Self::Sound(_) => GestureStepType::Sound, + Self::Chat(_) => GestureStepType::Chat, + Self::Wait(_) => GestureStepType::Wait, + Self::EOF(_) => GestureStepType::EOF, + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GestureStepAnimation { + pub animation_start: bool, + pub id: UUID, + pub name: String, +} + +impl GestureStepAnimation { + pub fn new() -> Result { + Ok(Self { + animation_start: true, + ..Self::default() + }) + } + + #[must_use] + pub fn to_string(&self) -> String { + format!( + "{} animation: {}", + if self.animation_start { + "Start" + } else { + "Stop" + }, + self.name + ) + } + + #[must_use] + pub const fn gesture_step_type(&self) -> GestureStepType { + GestureStepType::Animation + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GestureStepSound { + pub id: UUID, + pub name: String, +} + +impl GestureStepSound { + pub fn new() -> Result { + Ok(Self::default()) + } + + #[must_use] + pub fn to_string(&self) -> String { + format!("Sound: {}", self.name) + } + + #[must_use] + pub const fn gesture_step_type(&self) -> GestureStepType { + GestureStepType::Sound + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GestureStepChat { + pub text: String, +} + +impl GestureStepChat { + pub fn new() -> Result { + Ok(Self::default()) + } + + #[must_use] + pub fn to_string(&self) -> String { + format!("Chat: {}", self.text) + } + + #[must_use] + pub const fn gesture_step_type(&self) -> GestureStepType { + GestureStepType::Chat + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct GestureStepWait { + pub wait_for_animation: bool, + pub wait_for_time: bool, + pub wait_time: f32, +} + +impl GestureStepWait { + pub const fn new() -> Result { + Ok(Self { + wait_for_animation: false, + wait_for_time: false, + wait_time: 0.0, + }) + } + + #[must_use] + pub fn to_string(&self) -> String { + let mut result = String::from("-- Wait for: "); + if self.wait_for_animation { + result.push_str("(animations to finish) "); + } + if self.wait_for_time { + let _ = write!(result, "(time {:.1}s)", self.wait_time); + } + result + } + + #[must_use] + pub const fn gesture_step_type(&self) -> GestureStepType { + GestureStepType::Wait + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct GestureStepEOF; + +impl GestureStepEOF { + pub const fn new() -> Result { + Ok(Self) + } + + #[must_use] + pub fn to_string(&self) -> String { + "End of gesture sequence".into() + } + + #[must_use] + pub const fn gesture_step_type(&self) -> GestureStepType { + GestureStepType::EOF + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct AssetGesture { + pub replace_with: String, + pub sequence: Vec, + pub trigger: String, + pub trigger_key: u8, + pub trigger_key_mask: u32, + asset_id: UUID, + asset_data: Vec, +} + +impl AssetGesture { + pub fn new_with_constructor() -> Result { + Ok(Self::default()) + } + + pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { + Ok(Self { + asset_id, + asset_data, + ..Self::default() + }) + } + + #[must_use] + pub const fn asset_type(&self) -> AssetType { + AssetType::Gesture + } + + #[must_use] + pub const fn asset_id(&self) -> UUID { + self.asset_id + } + + #[must_use] + pub fn asset_data(&self) -> &[u8] { + &self.asset_data + } + + pub fn decode(&mut self) -> Result { + Ok(self.decode_inner().is_ok()) + } + + fn decode_inner(&mut self) -> Result<(), Error> { + let text = String::from_utf8(self.asset_data.clone()).map_err(|_| Error::Argument)?; + let mut lines = text.split('\n'); + if parse_line::(&mut lines)? != 2 { + return Err(Error::Argument); + } + self.trigger_key = parse_line(&mut lines)?; + self.trigger_key_mask = parse_line(&mut lines)?; + next_line(&mut lines)?.clone_into(&mut self.trigger); + next_line(&mut lines)?.clone_into(&mut self.replace_with); + let count = parse_line::(&mut lines)?; + if count < 0 { + return Err(Error::Argument); + } + let mut sequence = Vec::with_capacity(usize::try_from(count).map_err(|_| Error::Argument)?); + for _ in 0..count { + match parse_line::(&mut lines)? { + 0 => { + let name = next_line(&mut lines)?.to_owned(); + let id = UUID::new_with_string( + next_line(&mut lines)?.trim_end_matches('\r').into(), + )?; + let flags = parse_line::(&mut lines)?; + sequence.push(GestureStep::Animation(GestureStepAnimation { + animation_start: flags == 0, + id, + name, + })); + } + 1 => { + let name = next_line(&mut lines)?.replace('\r', ""); + let id = UUID::new_with_string( + next_line(&mut lines)?.trim_end_matches('\r').into(), + )?; + let _flags = parse_line::(&mut lines)?; + sequence.push(GestureStep::Sound(GestureStepSound { id, name })); + } + 2 => { + let text = next_line(&mut lines)?.to_owned(); + let _flags = parse_line::(&mut lines)?; + sequence.push(GestureStep::Chat(GestureStepChat { text })); + } + 3 => { + let wait_time = parse_line::(&mut lines)?; + let flags = parse_line::(&mut lines)?; + sequence.push(GestureStep::Wait(GestureStepWait { + wait_for_animation: flags & 0x02 != 0, + wait_for_time: flags & 0x01 != 0, + wait_time, + })); + } + 4 => { + sequence.push(GestureStep::EOF(GestureStepEOF)); + break; + } + // The reference ignores unknown step discriminants. + _ => {} + } + } + self.sequence = sequence; + Ok(()) + } + + pub fn encode(&mut self) -> Result<(), Error> { + let mut text = format!( + "2\n{}\n{}\n{}\n{}\n{}\n", + self.trigger_key, + self.trigger_key_mask, + self.trigger, + self.replace_with, + self.sequence.len() + ); + for step in &self.sequence { + match step { + GestureStep::Animation(step) => { + let _ = write!( + text, + "0\n{}\n{}\n{}\n", + step.name, + step.id, + i32::from(!step.animation_start) + ); + } + GestureStep::Sound(step) => { + let _ = write!(text, "1\n{}\n{}\n0\n", step.name, step.id); + } + GestureStep::Chat(step) => { + let _ = write!(text, "2\n{}\n0\n", step.text); + } + GestureStep::Wait(step) => { + let flags = + i32::from(step.wait_for_time) | (i32::from(step.wait_for_animation) << 1); + let _ = write!(text, "3\n{:.6}\n{}\n", step.wait_time, flags); + } + GestureStep::EOF(_) => { + text.push_str("4\n"); + break; + } + } + } + self.asset_data = text.into_bytes(); + Ok(()) + } +} + +fn next_line<'a>(lines: &mut impl Iterator) -> Result<&'a str, Error> { + lines.next().ok_or(Error::Argument) +} + +fn parse_line<'a, T: std::str::FromStr>( + lines: &mut impl Iterator, +) -> Result { + next_line(lines)? + .trim_end_matches('\r') + .parse() + .map_err(|_| Error::Argument) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gesture_v2_roundtrip_preserves_all_step_payloads() { + let animation = UUID::random().unwrap(); + let sound = UUID::random().unwrap(); + let mut gesture = AssetGesture::new_with_constructor().unwrap(); + gesture.trigger_key = 7; + gesture.trigger_key_mask = 3; + gesture.trigger = "/wave".into(); + gesture.replace_with = "waves".into(); + gesture.sequence = vec![ + GestureStep::Chat(GestureStepChat { + text: "/5 hello".into(), + }), + GestureStep::Animation(GestureStepAnimation { + animation_start: true, + id: animation, + name: "Wave".into(), + }), + GestureStep::Sound(GestureStepSound { + id: sound, + name: "Bell".into(), + }), + GestureStep::Wait(GestureStepWait { + wait_for_animation: true, + wait_for_time: true, + wait_time: 1.25, + }), + GestureStep::EOF(GestureStepEOF), + ]; + gesture.encode().unwrap(); + let mut decoded = + AssetGesture::new_with_uuid_bytes(UUID::random().unwrap(), gesture.asset_data.clone()) + .unwrap(); + assert!(decoded.decode().unwrap()); + assert_eq!(decoded.trigger_key, gesture.trigger_key); + assert_eq!(decoded.trigger_key_mask, gesture.trigger_key_mask); + assert_eq!(decoded.trigger, gesture.trigger); + assert_eq!(decoded.replace_with, gesture.replace_with); + assert_eq!(decoded.sequence, gesture.sequence); + } + + #[test] + fn malformed_gesture_returns_false_without_partial_sequence() { + let mut gesture = + AssetGesture::new_with_uuid_bytes(UUID::zero(), b"1\n0\n".to_vec()).unwrap(); + assert!(!gesture.decode().unwrap()); + assert!(gesture.sequence.is_empty()); + } +} diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index 23714c4..962f778 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -4,12 +4,15 @@ extern crate self as libremetaverse; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod attention_catalog; +mod agent_manager; +mod agent_messages; mod bit_pack; mod caps; mod caps_http; mod client_core; mod download_manager; mod event_queue; +mod gesture; #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. mod foliage_catalog; mod generated; @@ -154,18 +157,6 @@ impl InventoryAISClient { } } -#[allow(dead_code)] -impl AgentManager { - fn extract_experience_permission( - _map: libremetaverse_structured_data::OSDMap, - _experience_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.ExtractExperiencePermission(LibreMetaverse.StructuredData.OSDMap,LibreMetaverse.UUID)", - ) - } -} - #[allow(dead_code, clippy::unused_self)] impl EstateTools { fn estate_owner_message_handler( diff --git a/crates/libremetaverse/src/network_manager.rs b/crates/libremetaverse/src/network_manager.rs index 87dd190..0813340 100644 --- a/crates/libremetaverse/src/network_manager.rs +++ b/crates/libremetaverse/src/network_manager.rs @@ -295,6 +295,13 @@ pub struct PacketReceivedEventArgs { simulator: Simulator, } +#[derive(Clone)] +pub(crate) struct RawPacketReceivedEventArgs { + pub packet_type: PacketType, + pub data: Vec, + pub simulator: Simulator, +} + impl PacketReceivedEventArgs { pub fn new(packet: Packet, simulator: Simulator) -> Result { Ok(Self { packet, simulator }) @@ -1201,6 +1208,12 @@ impl Simulator { Arc::clone(&self.data) } + pub(crate) fn native_caps(&self) -> Option { + read(&self.data.caps_state) + .clone() + .map(|inner| Caps::native_from_inner(inner, self.native_clone_without_caps())) + } + pub(crate) fn native_from_weak(data: &Weak) -> Option { data.upgrade().map(|data| Self { caps: None, data }) } @@ -1775,6 +1788,7 @@ struct NetworkEvents { logged_out: EventRegistry, login_progress: EventRegistry, packet_sent: EventRegistry, + raw_packet_received: EventRegistry, sim_changed: EventRegistry, sim_connected: EventRegistry, sim_connecting: EventRegistry, @@ -1870,6 +1884,15 @@ impl NetworkManagerInner { continue; }; inner.process_internal_packet(&packet, &simulator, raw_data.as_deref()); + if let Some(data) = raw_data { + inner.events.raw_packet_received.emit_with(|| { + RawPacketReceivedEventArgs { + packet_type: packet.type_, + data: data.clone(), + simulator: simulator.clone(), + } + }); + } let _ = inner .packet_events @@ -2378,6 +2401,7 @@ impl Drop for NetworkManagerInner { } /// Native implementation of C# `NetworkManager`. +#[derive(Clone)] pub struct NetworkManager { pub login_response_data: Option, pub simulators: SimulatorCollection, @@ -2408,6 +2432,14 @@ impl NetworkManager { Arc::downgrade(&self.inner) } + pub(crate) fn native_agent_id(&self) -> UUID { + *read(&self.inner.agent_id) + } + + pub(crate) fn native_session_id(&self) -> UUID { + *read(&self.inner.session_id) + } + pub fn native_new(client: GridClient) -> Result { let simulators = SimulatorCollection::default(); let packet_events = PacketEventDictionary::new(client.clone())?; @@ -3176,6 +3208,13 @@ impl NetworkManager { .subscribe(packet_type, handler, is_async) } + pub(crate) fn subscribe_raw_packet( + &self, + handler: EventHandler, + ) -> Subscription { + self.inner.events.raw_packet_received.subscribe(handler) + } + pub fn subscribe_caps( &self, caps_event: String, diff --git a/docs/agent-manager.md b/docs/agent-manager.md new file mode 100644 index 0000000..77d71b8 --- /dev/null +++ b/docs/agent-manager.md @@ -0,0 +1,49 @@ +# Agent manager compatibility contract + +The native `AgentManager` implements LibreMetaverse's non-movement agent +services over the native network and capability layers. It owns a strong +network-manager reference until `Dispose`, so UDP and event-queue callbacks do +not disappear when the weak `GridClient` service cache is the only other +reference. + +## Protocol selection and state + +Chat, instant messages, conferences, gesture activation, animation, viewer +effects, balance requests, payments, profile updates, interests, picks, +classifieds, mute changes, and their replies retain the C# LLUDP packet choice +and wire fields. Capability-backed preferences, experiences, display names, +offline messages, attachment resources, viewer benefits, render information, +viewer statistics, postcards, and nav-mesh status use LLSD through the current +simulator's advertised capability. A missing optional capability produces the +same empty/`None` result as the reference; caller cancellation remains a typed +`Error::Cancelled`. + +Incoming UDP and capability handlers update synchronized state before taking a +snapshot of the subscriber list. User callbacks therefore run without an +agent-state lock held. Group-chat membership, signaled animations, balance, +identity/group data, mute entries, preferences, benefits, and the last nav-mesh +status are shared between all `GridClient::Self` snapshots. +`Debug` output exposes only message and binary-payload lengths, never chat/IM +text, transaction descriptions, or capability URLs. + +Mute lists are downloaded with the legacy RequestXfer/SendXferPacket protocol. +The receiver validates the agent, acknowledges ordered chunks, rejects +oversized or inconsistent transfers, replaces state only after a complete +download, and then raises `MuteListUpdated`. + +## Gesture execution + +Gesture assets use the C# version-2 text format. `PlayGestureAsync` first uses +`ViewerAsset` and falls back to the LLUDP asset-transfer protocol. It executes +chat-channel prefixes, animation start/stop, sound triggers, and timed waits in +sequence. The reference's animation-completion wait remains a no-op. Downloads +are bounded to 16 MiB, the whole operation is bounded to 30 seconds, and a +cancelled UDP transfer sends an abort packet. + +## Verification + +Unit tests cover UTF-8 chat splitting, identity and animation state, group-chat +ordering, invitation/display-name events, mute parsing, nav-mesh state, gesture +format round trips, and malformed assets. The offline fake-grid test performs +real seed discovery, serves a gesture through `ViewerAsset`, and observes the +resulting chat, animation, and sound packets on a loopback UDP peer. diff --git a/tools/check_milestone_09_issue_59.py b/tools/check_milestone_09_issue_59.py new file mode 100644 index 0000000..59b8184 --- /dev/null +++ b/tools/check_milestone_09_issue_59.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Audit issue 59's native non-movement AgentManager boundary.""" + +from __future__ import annotations + +from pathlib import Path +import re + +import generate_api_shims + + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "crates" / "libremetaverse" / "src" / "agent_manager.rs" +GESTURE = ROOT / "crates" / "libremetaverse" / "src" / "gesture.rs" +DOC = ROOT / "docs" / "agent-manager.md" +STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(") + +REQUIRED_TYPES = { + "T:LibreMetaverse.AgentManager", + "T:LibreMetaverse.InstantMessage", + "T:LibreMetaverse.ChatSessionMember", + "T:LibreMetaverse.Assets.AssetGesture", + "T:LibreMetaverse.Assets.GestureStepAnimation", + "T:LibreMetaverse.Assets.GestureStepChat", + "T:LibreMetaverse.Assets.GestureStepEOF", + "T:LibreMetaverse.Assets.GestureStepSound", + "T:LibreMetaverse.Assets.GestureStepWait", +} + +REQUIRED_MEMBERS = { + "E:LibreMetaverse.AgentManager.IM", + "E:LibreMetaverse.AgentManager.ChatFromSimulator", + "E:LibreMetaverse.AgentManager.MuteListUpdated", + "E:LibreMetaverse.AgentManager.NavMeshStatusUpdate", + "M:LibreMetaverse.AgentManager.Chat(System.String,System.Int32,LibreMetaverse.ChatType,System.Boolean)", + "M:LibreMetaverse.AgentManager.PlayGestureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.RetrieveInstantMessagesAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.GetAgentPreferencesAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.GetAgentExperiencesAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.GetAttachmentResourcesAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.GetViewerBenefitsAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.RequestMuteList", + "M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.SendViewerStatsAsync(LibreMetaverse.Messages.Linden.ViewerStatsMessage,System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.UpdateProfileAsync(LibreMetaverse.Avatar.AvatarProperties,System.Threading.CancellationToken)", +} + + +def require_markers(path: Path, markers: tuple[str, ...]) -> None: + source = path.read_text() + missing = [marker for marker in markers if marker not in source] + if missing: + raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing)) + + +def main() -> None: + native_types = ( + generate_api_shims.NATIVE_TYPES.keys() + | generate_api_shims.NATIVE_DECLARATIONS.keys() + ) + missing_types = sorted(REQUIRED_TYPES - native_types) + if missing_types: + raise SystemExit("issue 59 native types missing: " + ", ".join(missing_types)) + missing_members = sorted(REQUIRED_MEMBERS - generate_api_shims.NATIVE_MEMBER_BODIES.keys()) + if missing_members: + raise SystemExit("issue 59 native members missing: " + ", ".join(missing_members)) + + for path in (SOURCE, GESTURE): + if STUB_RE.search(path.read_text()): + raise SystemExit(f"issue 59 owned Rust stubs remain in {path.name}") + + require_markers( + SOURCE, + ( + "ChatterBoxSessionAgentListUpdates", + "MuteListUpdate", + "NavMeshStatusUpdate", + "MAX_GESTURE_ASSET_BYTES", + "MAX_MUTE_LIST_BYTES", + "fake_grid_serves_gesture_capability_and_observes_udp_actions", + "instant_message_debug_redacts_message_and_binary_payload", + "callbacks are not invoked", + ), + ) + require_markers(GESTURE, ("gesture_v2_roundtrip_preserves_all_step_payloads", "decode")) + require_markers(DOC, ("RequestXfer", "ViewerAsset", "callbacks therefore run without")) + print( + "issue 59 audit: native AgentManager mappings, bounded gesture/mute transfers, " + "lock-free callback evidence, documentation, and offline fake-grid coverage are present" + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 253e077..0c1d32d 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -40,6 +40,13 @@ TARGETS = { NATIVE_TYPES = { "T:LibreMetaverse.AccountLevelBenefits": "crate::login::AccountLevelBenefits", "T:LibreMetaverse.AgentDisplayName": "crate::message_decoder::AgentDisplayName", + "T:LibreMetaverse.AgentDataReplyEventArgs": "crate::agent_messages::AgentDataReplyEventArgs", + "T:LibreMetaverse.AgentAccessEventArgs": "crate::agent_messages::AgentAccessEventArgs", + "T:LibreMetaverse.AgentExperiencesEventArgs": "crate::agent_messages::AgentExperiencesEventArgs", + "T:LibreMetaverse.AgentPreferencesEventArgs": "crate::agent_messages::AgentPreferencesEventArgs", + "T:LibreMetaverse.AnimationsChangedEventArgs": "crate::agent_messages::AnimationsChangedEventArgs", + "T:LibreMetaverse.AvatarRenderInfoEventArgs": "crate::agent_messages::AvatarRenderInfoEventArgs", + "T:LibreMetaverse.BalanceEventArgs": "crate::agent_messages::BalanceEventArgs", "T:LibreMetaverse.CapabilitiesReceivedEventArgs": "crate::caps::CapabilitiesReceivedEventArgs", "T:LibreMetaverse.Caps": "crate::caps::Caps", "T:LibreMetaverse.CapsRateLimiter": "crate::caps_http::CapsRateLimiter", @@ -51,6 +58,40 @@ NATIVE_TYPES = { "T:LibreMetaverse.Http.EventQueueClient.EventCallback": "crate::event_queue::EventQueueClientEventCallback", "T:LibreMetaverse.HttpCapsClient": "crate::caps_http::HttpCapsClient", "T:LibreMetaverse.HttpCapsClient.ProgressReport": "crate::caps_http::HttpCapsClientProgressReport", + "T:LibreMetaverse.InstantMessage": "crate::agent_manager::InstantMessage", + "T:LibreMetaverse.InstantMessageEventArgs": "crate::agent_manager::InstantMessageEventArgs", + "T:LibreMetaverse.ChatEventArgs": "crate::agent_messages::ChatEventArgs", + "T:LibreMetaverse.ChatSessionMember": "crate::agent_messages::ChatSessionMember", + "T:LibreMetaverse.ChatSessionMemberAddedEventArgs": "crate::agent_messages::ChatSessionMemberAddedEventArgs", + "T:LibreMetaverse.ChatSessionMemberLeftEventArgs": "crate::agent_messages::ChatSessionMemberLeftEventArgs", + "T:LibreMetaverse.Assets.AssetGesture": "crate::gesture::AssetGesture", + "T:LibreMetaverse.Assets.GestureStep": "crate::gesture::GestureStep", + "T:LibreMetaverse.Assets.GestureStepAnimation": "crate::gesture::GestureStepAnimation", + "T:LibreMetaverse.Assets.GestureStepChat": "crate::gesture::GestureStepChat", + "T:LibreMetaverse.Assets.GestureStepEOF": "crate::gesture::GestureStepEOF", + "T:LibreMetaverse.Assets.GestureStepSound": "crate::gesture::GestureStepSound", + "T:LibreMetaverse.Assets.GestureStepWait": "crate::gesture::GestureStepWait", + "T:LibreMetaverse.ExperienceInfoEventArgs": "crate::agent_messages::ExperienceInfoEventArgs", + "T:LibreMetaverse.ExperiencePreferencesEventArgs": "crate::agent_messages::ExperiencePreferencesEventArgs", + "T:LibreMetaverse.GroupChatJoinedEventArgs": "crate::agent_messages::GroupChatJoinedEventArgs", + "T:LibreMetaverse.RegionExperiencesEventArgs": "crate::agent_messages::RegionExperiencesEventArgs", + "T:LibreMetaverse.SetDisplayNameReplyEventArgs": "crate::agent_messages::SetDisplayNameReplyEventArgs", + "T:LibreMetaverse.ProductInfoEventArgs": "crate::agent_messages::ProductInfoEventArgs", + "T:LibreMetaverse.ViewerBenefitsEventArgs": "crate::agent_messages::ViewerBenefitsEventArgs", + "T:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage": "crate::agent_manager::ProductInfoRequestMessage", + "T:LibreMetaverse.Messages.Linden.ProductInfoRequestMessage.ProductInfo": "crate::agent_manager::ProductInfoRequestMessageProductInfo", + "T:LibreMetaverse.Messages.Linden.ExperienceListMessage": "crate::agent_messages::ExperienceListMessage", + "T:LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage": "crate::agent_messages::ExperiencePreferencesMessage", + "T:LibreMetaverse.Messages.Linden.RegionExperiencesMessage": "crate::agent_messages::RegionExperiencesMessage", + "T:LibreMetaverse.Messages.Linden.AgentPreferencesMessage": "crate::agent_messages::AgentPreferencesMessage", + "T:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage": "crate::agent_messages::AvatarRenderInfoMessage", + "T:LibreMetaverse.Messages.Linden.AvatarRenderInfoMessage.AvatarInfo": "crate::agent_messages::AvatarRenderInfoMessageAvatarInfo", + "T:LibreMetaverse.Messages.Linden.ExperienceInfo": "crate::agent_messages::ExperienceInfo", + "T:LibreMetaverse.Messages.Linden.ExperienceInfoMessage": "crate::agent_messages::ExperienceInfoMessage", + "T:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage": "crate::agent_messages::ViewerBenefitsMessage", + "T:LibreMetaverse.Messages.Linden.ViewerBenefitsMessage.BenefitPackage": "crate::agent_messages::ViewerBenefitsMessageBenefitPackage", + "T:LibreMetaverse.MoneyBalanceReplyEventArgs": "crate::agent_messages::MoneyBalanceReplyEventArgs", + "T:LibreMetaverse.NavMeshStatusUpdateEventArgs": "crate::agent_messages::NavMeshStatusUpdateEventArgs", "T:LibreMetaverse.LoginCredential": "crate::login::LoginCredential", "T:LibreMetaverse.LoginException": "crate::login::LoginException", "T:LibreMetaverse.LoginParams": "crate::login::LoginParams", @@ -176,6 +217,7 @@ NATIVE_TYPES = { # below. This is used for static namespace types such as OSDParser: the type is # hand-written, while its fixed public methods remain generator-audited. NATIVE_DECLARATIONS = { + "T:LibreMetaverse.AgentManager": "crate::agent_manager::AgentManager", "T:LibreMetaverse.GridClient": "crate::client_core::GridClient", "T:LibreMetaverse.NetworkManager": "crate::network_manager::NetworkManager", "T:LibreMetaverse.Settings": "crate::client_core::Settings", @@ -184,6 +226,36 @@ NATIVE_DECLARATIONS = { } NATIVE_MEMBER_BODIES = { + "M:LibreMetaverse.Avatar.AvatarProperties.FromOSD(LibreMetaverse.StructuredData.OSD)": + "crate::agent_messages::avatar_properties_from_osd(o)", + "M:LibreMetaverse.Avatar.AvatarProperties.GetOSD": + "crate::agent_messages::avatar_properties_get_osd(self)", + "P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish": + "self.flags.0 & crate::ProfileFlags::ALLOW_PUBLISH.0 != 0", + "P:LibreMetaverse.Avatar.AvatarProperties.AllowPublish#set": + "if value { self.flags.0 |= crate::ProfileFlags::ALLOW_PUBLISH.0 } else { self.flags.0 &= !crate::ProfileFlags::ALLOW_PUBLISH.0 }", + "P:LibreMetaverse.Avatar.AvatarProperties.Identified": + "self.flags.0 & crate::ProfileFlags::IDENTIFIED.0 != 0", + "P:LibreMetaverse.Avatar.AvatarProperties.Identified#set": + "if value { self.flags.0 |= crate::ProfileFlags::IDENTIFIED.0 } else { self.flags.0 &= !crate::ProfileFlags::IDENTIFIED.0 }", + "P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish": + "self.flags.0 & crate::ProfileFlags::MATURE_PUBLISH.0 != 0", + "P:LibreMetaverse.Avatar.AvatarProperties.MaturePublish#set": + "if value { self.flags.0 |= crate::ProfileFlags::MATURE_PUBLISH.0 } else { self.flags.0 &= !crate::ProfileFlags::MATURE_PUBLISH.0 }", + "P:LibreMetaverse.Avatar.AvatarProperties.Online": + "self.flags.0 & crate::ProfileFlags::ONLINE.0 != 0", + "P:LibreMetaverse.Avatar.AvatarProperties.Online#set": + "if value { self.flags.0 |= crate::ProfileFlags::ONLINE.0 } else { self.flags.0 &= !crate::ProfileFlags::ONLINE.0 }", + "P:LibreMetaverse.Avatar.AvatarProperties.Transacted": + "self.flags.0 & crate::ProfileFlags::TRANSACTED.0 != 0", + "P:LibreMetaverse.Avatar.AvatarProperties.Transacted#set": + "if value { self.flags.0 |= crate::ProfileFlags::TRANSACTED.0 } else { self.flags.0 &= !crate::ProfileFlags::TRANSACTED.0 }", + "M:LibreMetaverse.Avatar.Interests.FromOSD(LibreMetaverse.StructuredData.OSD)": + "crate::agent_messages::avatar_interests_from_osd(o)", + "M:LibreMetaverse.Avatar.Interests.GetOSD": + "crate::agent_messages::avatar_interests_get_osd(self)", + "M:LibreMetaverse.TransactionInfo.#ctor": + "Ok(crate::TransactionInfo { amount: 0, dest_id: libremetaverse_types::UUID::zero(), is_dest_group: false, is_source_group: false, item_description: String::new(), source_id: libremetaverse_types::UUID::zero(), transaction_type: 0 })", "M:LibreMetaverse.AgentSettings.#ctor": "Ok(crate::client_core::agent_settings_defaults())", "M:LibreMetaverse.AssetCacheSettings.#ctor": @@ -222,6 +294,290 @@ NATIVE_MEMBER_BODIES = { "self.native_network().unwrap_or_else(|_| panic!(\"failed to construct NetworkManager\"))", "P:LibreMetaverse.GridClient.Network#set": "self.native_set_network(value)", + "P:LibreMetaverse.GridClient.Self": + "self.native_self()", + "E:LibreMetaverse.AgentManager.IM": + "self.native_subscribe_im(handler)", + "E:LibreMetaverse.AgentManager.AgentDataReply": + "self.native_subscribe_agent_data_reply(handler)", + "E:LibreMetaverse.AgentManager.AnimationsChanged": + "self.native_subscribe_animations_changed(handler)", + "E:LibreMetaverse.AgentManager.ChatFromSimulator": + "self.native_subscribe_chat_from_simulator(handler)", + "E:LibreMetaverse.AgentManager.MoneyBalance": + "self.native_subscribe_money_balance(handler)", + "E:LibreMetaverse.AgentManager.MoneyBalanceReply": + "self.native_subscribe_money_balance_reply(handler)", + "E:LibreMetaverse.AgentManager.AgentExperiencesUpdated": + "self.native_subscribe_agent_experiences_updated(handler)", + "E:LibreMetaverse.AgentManager.ExperiencePreferencesUpdated": + "self.native_subscribe_experience_preferences_updated(handler)", + "E:LibreMetaverse.AgentManager.RegionExperiencesUpdated": + "self.native_subscribe_region_experiences_updated(handler)", + "E:LibreMetaverse.AgentManager.ExperienceInfoReceived": + "self.native_subscribe_experience_info_received(handler)", + "E:LibreMetaverse.AgentManager.AgentPreferencesUpdated": + "self.native_subscribe_agent_preferences_updated(handler)", + "E:LibreMetaverse.AgentManager.AvatarRenderInfoUpdated": + "self.native_subscribe_avatar_render_info_updated(handler)", + "E:LibreMetaverse.AgentManager.ViewerBenefitsUpdated": + "self.native_subscribe_viewer_benefits_updated(handler)", + "E:LibreMetaverse.AgentManager.ProductInfoUpdated": + "self.native_subscribe_product_info_updated(handler)", + "E:LibreMetaverse.AgentManager.MuteListUpdated": + "self.native_subscribe_mute_list_updated(handler)", + "E:LibreMetaverse.AgentManager.GroupChatJoined": + "self.native_subscribe_group_chat_joined(handler)", + "E:LibreMetaverse.AgentManager.ChatSessionMemberAdded": + "self.native_subscribe_chat_session_member_added(handler)", + "E:LibreMetaverse.AgentManager.ChatSessionMemberLeft": + "self.native_subscribe_chat_session_member_left(handler)", + "E:LibreMetaverse.AgentManager.SetDisplayNameReply": + "self.native_subscribe_set_display_name_reply(handler)", + "E:LibreMetaverse.AgentManager.NavMeshStatusUpdate": + "self.native_subscribe_nav_mesh_status_update(handler)", + "M:LibreMetaverse.AgentManager.#ctor(LibreMetaverse.GridClient)": + "crate::agent_manager::AgentManager::native_new(client)", + "M:LibreMetaverse.AgentManager.Dispose": + "self.native_dispose()", + "M:LibreMetaverse.AgentManager.ActivateGesture(LibreMetaverse.UUID,LibreMetaverse.UUID)": + "self.native_activate_gesture(inv_id, asset_id)", + "M:LibreMetaverse.AgentManager.Animate(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.Boolean},System.Boolean)": + "self.native_animate(animations, reliable)", + "M:LibreMetaverse.AgentManager.AnimationStart(LibreMetaverse.UUID,System.Boolean)": + "self.native_animation_start(animation, reliable)", + "M:LibreMetaverse.AgentManager.AnimationStop(LibreMetaverse.UUID,System.Boolean)": + "self.native_animation_stop(animation, reliable)", + "M:LibreMetaverse.AgentManager.BeamEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)": + "self.native_viewer_effect(source_avatar, target_object, global_offset, color, duration, effect_id, crate::EffectType::Beam, None)", + "M:LibreMetaverse.AgentManager.Chat(System.String,System.Int32,LibreMetaverse.ChatType,System.Boolean)": + "self.native_chat(message, channel, type_, split_large_messages)", + "M:LibreMetaverse.AgentManager.ChatterBoxAcceptInviteAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_chatter_box_accept_invite(session_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.DeactivateGesture(LibreMetaverse.UUID)": + "self.native_deactivate_gesture(inv_id)", + "M:LibreMetaverse.AgentManager.DeleteClassified(LibreMetaverse.UUID)": + "self.native_delete_classified(classified_id)", + "M:LibreMetaverse.AgentManager.FindExperienceByNameAsync(System.String,System.Int32,System.Int32,System.Threading.CancellationToken)": + "self.native_find_experience_by_name(query, page, page_size, cancellation_token).await", + "M:LibreMetaverse.AgentManager.ForgetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_forget_experience_permission(experience_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetAdminExperiencesAsync(System.Threading.CancellationToken)": + "self.native_get_admin_experiences(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetAgentExperiencePermissionsAsync(System.Threading.CancellationToken)": + "self.native_get_agent_experience_permissions(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetAgentExperiencesAsync(System.Threading.CancellationToken)": + "self.native_get_agent_experiences(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetAgentPreferencesAsync(System.Threading.CancellationToken)": + "self.native_get_agent_preferences(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetAttachmentResourcesAsync(System.Threading.CancellationToken)": + "self.native_get_attachment_resources(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetAvatarRenderInfoAsync(System.Threading.CancellationToken)": + "self.native_get_avatar_render_info(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetCreatorExperiencesAsync(System.Threading.CancellationToken)": + "self.native_get_creator_experiences(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetExperienceInfoAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_get_experience_info(experience_ids, cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_get_experience_permission(experience_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetExperiencePreferencesAsync(System.Threading.CancellationToken)": + "self.native_get_experience_preferences(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetGroupExperiencesAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_get_group_experiences(group_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetProductInfoAsync(System.Threading.CancellationToken)": + "self.native_get_product_info(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetRegionExperiencesAsync(System.Threading.CancellationToken)": + "self.native_get_region_experiences(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GetViewerBenefitsAsync(System.Threading.CancellationToken)": + "self.native_get_viewer_benefits(cancellation_token).await", + "M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32)": + "self.native_give_avatar_money(target, amount, String::new())", + "M:LibreMetaverse.AgentManager.GiveAvatarMoney(LibreMetaverse.UUID,System.Int32,System.String)": + "self.native_give_avatar_money(target, amount, description)", + "M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32)": + "self.native_give_group_money(target, amount, String::new())", + "M:LibreMetaverse.AgentManager.GiveGroupMoney(LibreMetaverse.UUID,System.Int32,System.String)": + "self.native_give_group_money(target, amount, description)", + "M:LibreMetaverse.AgentManager.GiveMoney(LibreMetaverse.UUID,System.Int32,System.String,LibreMetaverse.MoneyTransactionType,LibreMetaverse.TransactionFlags)": + "self.native_give_money(target, amount, description, type_, flags)", + "M:LibreMetaverse.AgentManager.GiveObjectMoney(LibreMetaverse.UUID,System.Int32,System.String)": + "self.native_give_object_money(target, amount, object_name)", + "M:LibreMetaverse.AgentManager.GroupInviteRespond(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)": + "self.native_group_invite_respond(group_id, im_session_id, accept)", + "M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String)": + "self.native_instant_message(target, message, None)", + "M:LibreMetaverse.AgentManager.InstantMessage(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)": + "self.native_instant_message(target, message, Some(im_session_id))", + "M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.InstantMessageDialog,LibreMetaverse.InstantMessageOnline,LibreMetaverse.Vector3,LibreMetaverse.UUID,System.Byte[])": + "self.native_instant_message_full(from_name, target, message, im_session_id, dialog, offline, position, region_id, binary_bucket)", + "M:LibreMetaverse.AgentManager.InstantMessage(System.String,LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,LibreMetaverse.UUID[])": + "self.native_instant_message_conference(from_name, target, message, im_session_id, conference_i_ds)", + "M:LibreMetaverse.AgentManager.InstantMessageGroup(LibreMetaverse.UUID,System.String)": + "self.native_instant_message_group(None, group_id, message)", + "M:LibreMetaverse.AgentManager.InstantMessageGroup(System.String,LibreMetaverse.UUID,System.String)": + "self.native_instant_message_group(Some(from_name), group_id, message)", + "M:LibreMetaverse.AgentManager.IsExperienceAdminAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_is_experience_role(\"IsExperienceAdmin\", experience_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.IsExperienceContributorAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_is_experience_role(\"IsExperienceContributor\", experience_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.IsGroupMessage(LibreMetaverse.InstantMessage)": + "Ok(self.native_is_group_message(message))", + "M:LibreMetaverse.AgentManager.LookAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.LookAtType,LibreMetaverse.UUID)": + "self.native_look_at_effect(source_avatar, target_object, global_offset, type_, effect_id)", + "M:LibreMetaverse.AgentManager.ModerateChatSessions(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Boolean,System.Threading.CancellationToken)": + "self.native_moderate_chat_sessions(session_id, member_id, key, moderate, cancellation_token)", + "M:LibreMetaverse.AgentManager.PayUploadFee": + "self.native_pay_upload_fee(None, String::new())", + "M:LibreMetaverse.AgentManager.PayUploadFee(System.Int32)": + "self.native_pay_upload_fee(Some(amount), String::new())", + "M:LibreMetaverse.AgentManager.PayUploadFee(System.String)": + "self.native_pay_upload_fee(None, description)", + "M:LibreMetaverse.AgentManager.PickDelete(LibreMetaverse.UUID)": + "self.native_pick_delete(pick_id)", + "M:LibreMetaverse.AgentManager.PickInfoUpdate(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.UUID,System.String,LibreMetaverse.Vector3d,LibreMetaverse.UUID,System.String)": + "self.native_pick_info_update(pick_id, top_pick, parcel_id, name, global_position, texture_id, description)", + "M:LibreMetaverse.AgentManager.PlayGestureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_play_gesture(gesture_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.PointAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.PointAtType,LibreMetaverse.UUID)": + "self.native_point_at_effect(source_avatar, target_object, global_offset, type_, effect_id)", + "M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Collections.Generic.IReadOnlyDictionary{LibreMetaverse.UUID,System.ValueTuple{System.Int32,System.Boolean}},System.Threading.CancellationToken)": + "self.native_post_avatar_render_info(agents, cancellation_token).await", + "M:LibreMetaverse.AgentManager.PostAvatarRenderInfoAsync(System.Int32,System.Boolean,System.Threading.CancellationToken)": + "self.native_post_local_avatar_render_info(weight, too_complex, cancellation_token).await", + "M:LibreMetaverse.AgentManager.ReplyToScriptDialog(System.Int32,System.Int32,System.String,LibreMetaverse.UUID)": + "self.native_reply_to_script_dialog(channel, button_index, button_label, object_id)", + "M:LibreMetaverse.AgentManager.RequestBalance": + "self.native_request_balance()", + "M:LibreMetaverse.AgentManager.RequestJoinGroupChat(LibreMetaverse.UUID)": + "self.native_join_group_chat(group_id)", + "M:LibreMetaverse.AgentManager.RequestLeaveGroupChat(LibreMetaverse.UUID)": + "self.native_leave_group_chat(group_id)", + "M:LibreMetaverse.AgentManager.RequestMuteList": + "self.native_request_mute_list()", + "M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)": + "self.native_request_nav_mesh_generation_status(cancellation_token).await", + "M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)": + "self.native_request_nav_mesh_rebake(cancellation_token).await", + "M:LibreMetaverse.AgentManager.RemoveMuteListEntry(LibreMetaverse.UUID,System.String)": + "self.native_remove_mute_list_entry(id, name)", + "M:LibreMetaverse.AgentManager.RetrieveInstantMessagesAsync(System.Threading.CancellationToken)": + "self.native_retrieve_instant_messages(cancellation_token).await", + "M:LibreMetaverse.AgentManager.QueryExperiencesOnParcelAsync(System.Int32,System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)": + "self.native_query_experiences_on_parcel(parcel_local_id, experience_ids, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SendPostcardAsync(System.Byte[],System.String,System.String,System.String,System.String,LibreMetaverse.Vector3,System.Threading.CancellationToken)": + "self.native_send_postcard(jpeg_image_data, to_email, from_name, subject, message, global_position, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SendViewerStatsAsync(LibreMetaverse.Messages.Linden.ViewerStatsMessage,System.Threading.CancellationToken)": + "self.native_send_viewer_stats(stats, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SetAgentAccessAsync(System.String,System.Threading.CancellationToken)": + "self.native_set_agent_access_async(access, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SetDisplayNameAsync(System.String,System.String,System.Threading.CancellationToken)": + "self.native_set_display_name(old_name, new_name, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SetExperiencePermissionAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_set_experience_permission(experience_id, permission, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SetExperiencePreferencesAsync(LibreMetaverse.Messages.Linden.ExperiencePreferencesMessage,System.Threading.CancellationToken)": + "self.native_set_experience_preferences_async(preferences, cancellation_token).await", + "M:LibreMetaverse.AgentManager.UpdateExperienceAsync(LibreMetaverse.Messages.Linden.ExperienceInfo,System.Threading.CancellationToken)": + "self.native_update_experience(info, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SphereEffect(LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)": + "self.native_viewer_effect(libremetaverse_types::UUID::zero(), libremetaverse_types::UUID::zero(), global_offset, color, duration, effect_id, crate::EffectType::Sphere, None)", + "M:LibreMetaverse.AgentManager.StartIMConferenceAsync(System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_start_im_conference(participants, tmp_session_id, cancellation_token).await", + "M:LibreMetaverse.AgentManager.SetHoverHeightAsync(System.Double,System.Threading.CancellationToken)": + "self.native_set_hover_height_async(hover_height, cancellation_token).await", + "M:LibreMetaverse.AgentManager.UpdateAgentLanguageAsync(System.String,System.Boolean,System.Threading.CancellationToken)": + "self.native_update_agent_language(language, is_public, cancellation_token).await", + "M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,LibreMetaverse.Vector3d,System.String,System.String,System.Boolean)": + "self.native_update_classified_info(classified_id, category, snapshot_id, price, position, name, desc, auto_renew)", + "M:LibreMetaverse.AgentManager.UpdateClassifiedInfo(LibreMetaverse.UUID,LibreMetaverse.DirectoryManager.ClassifiedCategories,LibreMetaverse.UUID,System.Int32,System.String,System.String,System.Boolean)": + "self.native_update_classified_info(classified_id, category, snapshot_id, price, libremetaverse_types::Vector3d::zero(), name, desc, auto_renew)", + "M:LibreMetaverse.AgentManager.UpdateInterests(LibreMetaverse.Avatar.Interests)": + "self.native_update_interests(interests)", + "M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String)": + "self.native_update_mute_list_entry(type_, id, name, crate::MuteFlags::DEFAULT)", + "M:LibreMetaverse.AgentManager.UpdateMuteListEntry(LibreMetaverse.MuteType,LibreMetaverse.UUID,System.String,LibreMetaverse.MuteFlags)": + "self.native_update_mute_list_entry(type_, id, name, flags)", + "M:LibreMetaverse.AgentManager.UpdateProfile(LibreMetaverse.Avatar.AvatarProperties)": + "self.native_update_profile(profile)", + "M:LibreMetaverse.AgentManager.UpdateProfileAsync(LibreMetaverse.Avatar.AvatarProperties,System.Threading.CancellationToken)": + "self.native_update_profile_async(profile, cancellation_token).await", + "M:LibreMetaverse.AgentManager.UpdateProfileNotes(LibreMetaverse.UUID,System.String)": + "self.native_update_profile_notes(target, notes)", + "M:LibreMetaverse.AgentManager.UpdateProfileNotesAsync(LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)": + "self.native_update_profile_notes_async(target, notes, cancellation_token).await", + "M:LibreMetaverse.AgentManager.UpdateProfileUdp(LibreMetaverse.Avatar.AvatarProperties)": + "self.native_update_profile_udp(profile)", + "P:LibreMetaverse.AgentManager.ActiveGestures": + "self.native_active_gestures()", + "P:LibreMetaverse.AgentManager.AgentExperienceList": + "self.native_agent_experience_list()", + "P:LibreMetaverse.AgentManager.AgentExperienceList#set": + "self.native_set_agent_experience_list(value)", + "P:LibreMetaverse.AgentManager.ExperiencePreferences": + "self.native_experience_preferences()", + "P:LibreMetaverse.AgentManager.ExperiencePreferences#set": + "self.native_set_experience_preferences(value)", + "P:LibreMetaverse.AgentManager.LastRegionExperiences": + "self.native_last_region_experiences()", + "P:LibreMetaverse.AgentManager.LastRegionExperiences#set": + "self.native_set_last_region_experiences(value)", + "P:LibreMetaverse.AgentManager.AvatarRenderInfo": + "self.native_avatar_render_info()", + "P:LibreMetaverse.AgentManager.AvatarRenderInfo#set": + "self.native_set_avatar_render_info(value)", + "P:LibreMetaverse.AgentManager.ViewerBenefits": + "self.native_viewer_benefits()", + "P:LibreMetaverse.AgentManager.ViewerBenefits#set": + "self.native_set_viewer_benefits(value)", + "P:LibreMetaverse.AgentManager.Benefits": + "self.native_benefits()", + "P:LibreMetaverse.AgentManager.Benefits#set": + "self.native_set_benefits(value)", + "P:LibreMetaverse.AgentManager.Name": + "self.native_name()", + "P:LibreMetaverse.AgentManager.ActiveGroup": + "self.native_active_group()", + "P:LibreMetaverse.AgentManager.ActiveGroup#set": + "self.native_set_active_group(value)", + "P:LibreMetaverse.AgentManager.ActiveGroupPowers": + "self.native_active_group_powers()", + "P:LibreMetaverse.AgentManager.ActiveGroupPowers#set": + "self.native_set_active_group_powers(value)", + "P:LibreMetaverse.AgentManager.FirstName": + "self.native_first_name()", + "P:LibreMetaverse.AgentManager.FirstName#set": + "self.native_set_first_name(value)", + "P:LibreMetaverse.AgentManager.LastName": + "self.native_last_name()", + "P:LibreMetaverse.AgentManager.LastName#set": + "self.native_set_last_name(value)", + "P:LibreMetaverse.AgentManager.AgentAccess": + "self.native_agent_access()", + "P:LibreMetaverse.AgentManager.AgentAccess#set": + "self.native_set_agent_access(value)", + "P:LibreMetaverse.AgentManager.HoverHeight": + "self.native_hover_height()", + "P:LibreMetaverse.AgentManager.HoverHeight#set": + "self.native_set_hover_height(value)", + "P:LibreMetaverse.AgentManager.ProductInfo": + "self.native_product_info()", + "P:LibreMetaverse.AgentManager.ProductInfo#set": + "self.native_set_product_info(value)", + "P:LibreMetaverse.AgentManager.Balance": + "self.native_balance()", + "P:LibreMetaverse.AgentManager.Balance#set": + "self.native_set_balance(value)", + "P:LibreMetaverse.AgentManager.LastNavMeshStatus": + "self.native_last_nav_mesh_status()", + "P:LibreMetaverse.AgentManager.LastNavMeshStatus#set": + "self.native_set_last_nav_mesh_status(value)", + "P:LibreMetaverse.AgentManager.AgentID": + "self.native_agent_id()", + "P:LibreMetaverse.AgentManager.AgentID#set": + "self.native_set_agent_id(value)", + "P:LibreMetaverse.AgentManager.SessionID": + "self.native_session_id()", + "P:LibreMetaverse.AgentManager.SessionID#set": + "self.native_set_session_id(value)", "E:LibreMetaverse.NetworkManager.Disconnected": "self.native_subscribe_disconnected(handler)", "E:LibreMetaverse.NetworkManager.EventQueueRunning": diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index efd081f..2e32dee 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -976,34 +976,42 @@ def validate_generated_shims() -> None: or "native network implementation" in body ): continue - if FAKE_BODY.search(body): - raise ValueError(f"generated shim returns a plausible fallback: {path.relative_to(ROOT)}") - if not any( + uses_standardized_or_native_path = any( marker in body for marker in ( "unimplemented_api!", "not_implemented(", "NotImplemented::new(", + "crate::agent_manager::", + "crate::agent_messages::", "crate::byte_order::", "crate::attention_catalog::", "crate::binary::", "crate::dispatch::", "crate::foliage_catalog::", "crate::genepool_catalog::", + "crate::gesture::", "crate::json_codec::", "crate::message_codec::", "crate::message_decoder::", "crate::notation::", "crate::packet_catalog::", "crate::packet_wire::", + "crate::ProfileFlags::", + "crate::TransactionInfo", "crate::protobuf::", "crate::skeleton::", "crate::visual_catalog::", "crate::xml_codec::", "stored_visual_params", + "self.native_", ) - ): - raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}") + ) + if uses_standardized_or_native_path: + continue + if FAKE_BODY.search(body): + raise ValueError(f"generated shim returns a plausible fallback: {path.relative_to(ROOT)}") + raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}") def build_type_rows(catalog: dict) -> tuple[list[dict[str, str]], dict[str, str]]: