From 0791e0a69e8e1ec7164808516fb71dcce0affa18 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sun, 9 Aug 2026 23:07:30 +0000 Subject: [PATCH] Implement native agent movement and teleportation (#60) --- .gitea/workflows/codegen.yml | 7 + .gitea/workflows/rust-workspace.yml | 6 +- README.md | 9 + api/SHIM-COVERAGE.md | 2 +- crates/libremetaverse/src/agent_manager.rs | 81 +- crates/libremetaverse/src/agent_movement.rs | 3479 ++++++++++++++++++ crates/libremetaverse/src/generated.rs | 1097 +----- crates/libremetaverse/src/lib.rs | 1 + crates/libremetaverse/src/network_manager.rs | 15 + docs/agent-movement.md | 65 + tools/check_milestone_09_issue_60.py | 106 + tools/generate_api_shims.py | 127 + tools/generate_rust_mapping.py | 1 + 13 files changed, 4060 insertions(+), 936 deletions(-) create mode 100644 crates/libremetaverse/src/agent_movement.rs create mode 100644 docs/agent-movement.md create mode 100644 tools/check_milestone_09_issue_60.py diff --git a/.gitea/workflows/codegen.yml b/.gitea/workflows/codegen.yml index c898211..752c2fd 100644 --- a/.gitea/workflows/codegen.yml +++ b/.gitea/workflows/codegen.yml @@ -14,6 +14,9 @@ on: - "tools/check_codegen_gate.py" - "tools/check_milestone_08.py" - "tools/check_milestone_09_issue_59.py" + - "tools/check_milestone_09_issue_60.py" + - "crates/libremetaverse/src/agent_movement.rs" + - "docs/agent-movement.md" - "crates/libremetaverse/Cargo.toml" - "crates/libremetaverse/src/generated.rs" - "crates/libremetaverse/src/packets.rs" @@ -39,6 +42,9 @@ on: - "tools/check_codegen_gate.py" - "tools/check_milestone_08.py" - "tools/check_milestone_09_issue_59.py" + - "tools/check_milestone_09_issue_60.py" + - "crates/libremetaverse/src/agent_movement.rs" + - "docs/agent-movement.md" - "crates/libremetaverse/Cargo.toml" - "crates/libremetaverse/src/generated.rs" - "crates/libremetaverse/src/packets.rs" @@ -72,6 +78,7 @@ jobs: python3 tools/check_api_coverage.py python3 tools/check_milestone_08.py python3 tools/check_milestone_09_issue_59.py + python3 tools/check_milestone_09_issue_60.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 0207968..703a826 100644 --- a/.gitea/workflows/rust-workspace.yml +++ b/.gitea/workflows/rust-workspace.yml @@ -25,7 +25,9 @@ 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: Audit native AgentManager boundaries + run: | + python3 tools/check_milestone_09_issue_59.py + python3 tools/check_milestone_09_issue_60.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 f175984..482ab81 100644 --- a/README.md +++ b/README.md @@ -339,3 +339,12 @@ 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). + +Native movement now covers all control flags, camera frame math, periodic and +manual `AgentUpdate`, flying/crouching/jumping/sitting/standing, always-run, +FOV, and autopilot packets. Teleport requests and UDP/CAPS progress messages +share cancellation-safe waiters, while region handoffs use a bounded, +diagnosable crossing state machine with recovery to the old simulator. Paused +time tests and a loopback fake grid verify exact wire fields and clean worker +shutdown. The compatibility and lifecycle contract is documented in +[`docs/agent-movement.md`](docs/agent-movement.md). diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 1992d7a..edc1ead 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: 121 types / 14,470 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 128 types / 14,611 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 index 872d715..d4f579f 100644 --- a/crates/libremetaverse/src/agent_manager.rs +++ b/crates/libremetaverse/src/agent_manager.rs @@ -25,19 +25,18 @@ use crate::{ ViewerBenefitsEventArgs, }; use crate::{ - AgentFlags, AgentManagerAgentMovement, AgentManagerAgentMovementAgentCamera, AgentState, - ChatSessionMember, ChatType, EffectType, Error, GridClient, InstantMessageDialog, - InstantMessageOnline, LookAtType, MoneyTransactionType, MuteEntry, PointAtType, Simulator, - TransactionFlags, network_manager::CapsEventQueueCallback, + AgentManagerAgentMovement, 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 libremetaverse_types::{Color4, 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::sync::{Arc, Mutex, OnceLock, RwLock}; use std::time::{Duration, Instant, UNIX_EPOCH}; const MAX_CHAT_BYTES: usize = 1023; @@ -127,12 +126,12 @@ fn parse_mute_list(text: &str) -> HashMap { result } -struct EventRegistryState { +pub(crate) struct EventRegistryState { next_id: AtomicU64, handlers: Mutex)>>, } -struct EventRegistry { +pub(crate) struct EventRegistry { state: Arc>, } @@ -148,7 +147,7 @@ impl Default for EventRegistry { } impl EventRegistry { - fn subscribe(&self, handler: EventHandler) -> Subscription { + pub(crate) 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); @@ -159,7 +158,7 @@ impl EventRegistry { }) } - fn emit(&self, value: T) { + pub(crate) fn emit(&self, value: T) { let handlers: Vec<_> = mutex(&self.state.handlers) .iter() .map(|(_, handler)| Arc::clone(handler)) @@ -345,6 +344,7 @@ pub(crate) struct AgentManagerInner { network: Mutex>, raw_packet_subscription: Mutex>, caps_subscriptions: Mutex>, + movement: OnceLock>, chat_from_simulator: EventRegistry, money_balance: EventRegistry, money_balance_reply: EventRegistry, @@ -397,6 +397,7 @@ impl Default for AgentManagerInner { network: Mutex::new(None), raw_packet_subscription: Mutex::new(None), caps_subscriptions: Mutex::new(Vec::new()), + movement: OnceLock::new(), chat_from_simulator: EventRegistry::default(), money_balance: EventRegistry::default(), money_balance_reply: EventRegistry::default(), @@ -420,6 +421,13 @@ impl Default for AgentManagerInner { impl AgentManagerInner { fn handle_raw_packet(&self, event: crate::network_manager::RawPacketReceivedEventArgs) { + if let Some(movement) = self.movement.get() + && movement + .handle_raw_packet(event.packet_type, &event.data, event.simulator.clone()) + .unwrap_or(false) + { + return; + } let result = match event.packet_type { crate::packets::PacketType::ChatFromSimulator => { self.handle_chat_packet(&event.data, event.simulator) @@ -716,6 +724,29 @@ impl AgentManagerInner { } } + pub(crate) fn native_ids(&self, network: &crate::NetworkManager) -> (UUID, UUID) { + let agent_id = *self + .agent_id + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let session_id = *self + .session_id + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + ( + if agent_id == UUID::zero() { + network.native_agent_id() + } else { + agent_id + }, + if session_id == UUID::zero() { + network.native_session_id() + } else { + session_id + }, + ) + } + fn handle_caps_event( self: &Arc, client: Arc, @@ -723,6 +754,13 @@ impl AgentManagerInner { message: &dyn crate::interfaces::IMessage, simulator: Simulator, ) { + if let Some(movement) = self.movement.get() + && movement + .handle_caps_event(name, message, simulator.clone()) + .unwrap_or(false) + { + return; + } let result = match name { "ChatterBoxInvitation" => self.handle_chat_invitation(client, message, simulator), "ChatterBoxSessionEventReply" => self.handle_chat_session_event_reply(client, message), @@ -926,7 +964,7 @@ pub struct AgentManager { pub movement: AgentManagerAgentMovement, pub mute_list: Arc>>, pub signaled_animations: Arc>>, - client: Arc, + pub(crate) client: Arc, inner: Arc, } @@ -980,6 +1018,9 @@ impl AgentManager { "ChatterBoxSessionAgentListUpdates", "SetDisplayNameReply", "NavMeshStatusUpdate", + "TeleportFinish", + "TeleportFailed", + "CrossedRegion", ] { let weak_inner = Arc::downgrade(&inner); let weak_client = Arc::downgrade(&client); @@ -996,16 +1037,17 @@ impl AgentManager { } } } + let movement_runtime = Arc::clone(inner.movement.get_or_init(|| { + crate::agent_movement::AgentMovementRuntime::new( + Arc::clone(&client), + network.clone(), + Arc::downgrade(&inner), + ) + })); 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, - }, + movement: AgentManagerAgentMovement::from_runtime(movement_runtime), mute_list: Arc::clone(&inner.mute_list), signaled_animations: Arc::clone(&inner.signaled_animations), client, @@ -1040,6 +1082,9 @@ impl AgentManager { } mutex(&self.inner.raw_packet_subscription).take(); mutex(&self.inner.caps_subscriptions).clear(); + if let Some(movement) = self.inner.movement.get() { + movement.stop(); + } mutex(&self.inner.network).take(); mutex(&self.inner.mute_xfers).clear(); self.group_chat_sessions diff --git a/crates/libremetaverse/src/agent_movement.rs b/crates/libremetaverse/src/agent_movement.rs new file mode 100644 index 0000000..90633c8 --- /dev/null +++ b/crates/libremetaverse/src/agent_movement.rs @@ -0,0 +1,3479 @@ +//! Native agent movement, camera, teleport, and region-crossing behavior. + +#![allow(clippy::cast_possible_truncation)] // C# camera math narrows doubles to singles. +#![allow(clippy::cast_precision_loss)] // C# compares region dimensions in single precision. +#![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)] // AgentUpdate is a protocol-shaped method. + +use crate::agent_manager::{AgentManager, AgentManagerInner, EventRegistry}; +use crate::interfaces::IMessage; +use crate::packet_catalog::GeneratedPacket; +use crate::packets::{ + AgentFOVPacket, AgentMovementCompletePacket, AgentRequestSitPacket, AgentSitPacket, + AgentUpdatePacket, CameraConstraintPacket, CrossedRegionPacket, GenericMessagePacket, + GenericMessagePacketParamListBlock, PacketType, SetAlwaysRunPacket, StartLurePacket, + StartLurePacketTargetDataBlock, TeleportCancelPacket, TeleportFailedPacket, + TeleportFinishPacket, TeleportLandmarkRequestPacket, TeleportLocalPacket, + TeleportLocationRequestPacket, TeleportLureRequestPacket, TeleportProgressPacket, + TeleportStartPacket, +}; +use crate::{ + AgentFlags, AgentManagerControlFlags, AgentManagerCrossingFailureReason, + AgentManagerCrossingState, AgentState, BorderCrossingDirection, Error, GridClient, + InstantMessageDialog, InstantMessageOnline, NetworkManager, Simulator, TeleportFlags, + TeleportStatus, +}; +use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri}; +use libremetaverse_types::{Quaternion, UUID, Vector3, Vector3d, Vector4}; +use std::collections::HashMap; +use std::fmt; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering}; +use std::sync::{Arc, Condvar, Mutex, RwLock, Weak}; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant, SystemTime}; + +fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { + value + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn read(value: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { + value + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { + value + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + +fn wire_string(mut bytes: Vec) -> String { + while bytes.last() == Some(&0) { + bytes.pop(); + } + String::from_utf8_lossy(&bytes).into_owned() +} + +fn decode_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::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 send_encoded( + simulator: &Simulator, + packet_type: PacketType, + mut data: Vec, + reliable: Option, +) -> Result<(), Error> { + if let Some(reliable) = reliable { + let flags = data.first_mut().ok_or(Error::Argument)?; + if reliable { + *flags |= crate::Helpers::MSG_RELIABLE; + } else { + *flags &= !crate::Helpers::MSG_RELIABLE; + } + } + 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 normalize(value: Vector3) -> Vector3 { + let length_squared = value.x * value.x + value.y * value.y + value.z * value.z; + if length_squared > 0.000_000_1 { + let inverse = 1.0 / length_squared.sqrt(); + Vector3 { + x: value.x * inverse, + y: value.y * inverse, + z: value.z * inverse, + } + } else { + Vector3::zero() + } +} + +fn cross(lhs: Vector3, rhs: Vector3) -> Vector3 { + Vector3 { + x: lhs.y * rhs.z - lhs.z * rhs.y, + y: lhs.z * rhs.x - lhs.x * rhs.z, + z: lhs.x * rhs.y - lhs.y * rhs.x, + } +} + +fn dot(lhs: Vector3, rhs: Vector3) -> f32 { + lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z +} + +fn subtract(lhs: Vector3, rhs: Vector3) -> Vector3 { + Vector3 { + x: lhs.x - rhs.x, + y: lhs.y - rhs.y, + z: lhs.z - rhs.z, + } +} + +fn add(lhs: Vector3, rhs: Vector3) -> Vector3 { + Vector3 { + x: lhs.x + rhs.x, + y: lhs.y + rhs.y, + z: lhs.z + rhs.z, + } +} + +fn scale(value: Vector3, factor: f32) -> Vector3 { + Vector3 { + x: value.x * factor, + y: value.y * factor, + z: value.z * factor, + } +} + +fn rotate_axis(value: Vector3, axis: Vector3, angle: f32) -> Vector3 { + let axis = normalize(axis); + let cosine = angle.cos(); + let sine = angle.sin(); + add( + add(scale(value, cosine), scale(cross(axis, value), sine)), + scale(axis, dot(axis, value) * (1.0 - cosine)), + ) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TeleportEventArgs { + message: String, + status: TeleportStatus, + flags: TeleportFlags, +} + +impl TeleportEventArgs { + pub fn new( + message: String, + status: TeleportStatus, + flags: TeleportFlags, + ) -> Result { + Ok(Self { + message, + status, + flags, + }) + } + + #[must_use] + pub const fn flags(&self) -> TeleportFlags { + self.flags + } + + #[must_use] + pub fn message(&self) -> String { + self.message.clone() + } + + #[must_use] + pub const fn status(&self) -> TeleportStatus { + self.status + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct CameraConstraintEventArgs { + collide_plane: Vector4, +} + +impl CameraConstraintEventArgs { + pub const fn new(collide_plane: Vector4) -> Result { + Ok(Self { collide_plane }) + } + + #[must_use] + pub const fn collide_plane(&self) -> Vector4 { + self.collide_plane + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AvatarSitResponseEventArgs { + object_id: UUID, + auto_pilot: bool, + camera_at_offset: Vector3, + camera_eye_offset: Vector3, + force_mouselook: bool, + sit_position: Vector3, + sit_rotation: Quaternion, +} + +#[derive(Clone, Debug)] +pub struct RegionCrossedEventArgs { + old_simulator: Option, + new_simulator: Option, +} + +#[derive(Clone, Debug)] +pub struct RegionCrossingPredictionEventArgs { + current_simulator: Simulator, + direction: BorderCrossingDirection, + time_until_crossing: f32, +} + +impl RegionCrossingPredictionEventArgs { + pub const fn new( + current_simulator: Simulator, + direction: BorderCrossingDirection, + time_until_crossing: f32, + ) -> Result { + Ok(Self { + current_simulator, + direction, + time_until_crossing, + }) + } + + #[must_use] + pub fn current_simulator(&self) -> Simulator { + self.current_simulator.clone() + } + + #[must_use] + pub const fn direction(&self) -> BorderCrossingDirection { + self.direction + } + + #[must_use] + pub const fn time_until_crossing(&self) -> f32 { + self.time_until_crossing + } +} + +impl RegionCrossedEventArgs { + pub const fn new( + old_simulator: Option, + new_simulator: Option, + ) -> Result { + Ok(Self { + old_simulator, + new_simulator, + }) + } + + #[must_use] + pub fn new_simulator(&self) -> Option { + self.new_simulator.clone() + } + + #[must_use] + pub fn old_simulator(&self) -> Option { + self.old_simulator.clone() + } +} + +impl AvatarSitResponseEventArgs { + pub const fn new( + object_id: UUID, + auto_pilot: bool, + camera_at_offset: Vector3, + camera_eye_offset: Vector3, + force_mouselook: bool, + sit_position: Vector3, + sit_rotation: Quaternion, + ) -> Result { + Ok(Self { + object_id, + auto_pilot, + camera_at_offset, + camera_eye_offset, + force_mouselook, + sit_position, + sit_rotation, + }) + } + + #[must_use] + pub const fn autopilot(&self) -> bool { + self.auto_pilot + } + + #[must_use] + pub const fn camera_at_offset(&self) -> Vector3 { + self.camera_at_offset + } + + #[must_use] + pub const fn camera_eye_offset(&self) -> Vector3 { + self.camera_eye_offset + } + + #[must_use] + pub const fn force_mouselook(&self) -> bool { + self.force_mouselook + } + + #[must_use] + pub const fn object_id(&self) -> UUID { + self.object_id + } + + #[must_use] + pub const fn sit_position(&self) -> Vector3 { + self.sit_position + } + + #[must_use] + pub const fn sit_rotation(&self) -> Quaternion { + self.sit_rotation + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct CameraFrame { + origin: Vector3, + x_axis: Vector3, + y_axis: Vector3, + z_axis: Vector3, +} + +impl Default for CameraFrame { + fn default() -> Self { + Self { + origin: Vector3 { + x: 128.0, + y: 128.0, + z: 20.0, + }, + x_axis: Vector3::unit_x(), + y_axis: Vector3::unit_y(), + z_axis: Vector3::unit_z(), + } + } +} + +impl CameraFrame { + fn finite(value: Vector3) -> bool { + value.x.is_finite() && value.y.is_finite() && value.z.is_finite() + } + + fn orthonormalize(&mut self) { + self.x_axis = normalize(self.x_axis); + self.y_axis = normalize(subtract( + self.y_axis, + scale(self.x_axis, dot(self.x_axis, self.y_axis)), + )); + self.z_axis = cross(self.x_axis, self.y_axis); + } + + fn rotate(&mut self, angle: f32, axis: Vector3) { + self.x_axis = rotate_axis(self.x_axis, axis, angle); + self.y_axis = rotate_axis(self.y_axis, axis, angle); + self.orthonormalize(); + } + + fn look_direction(&mut self, mut at: Vector3, up: Vector3) { + let mut left = cross(up, at); + if left == Vector3::zero() { + at.x += 0.01; + at = normalize(at); + left = cross(up, at); + } + self.x_axis = at; + self.y_axis = normalize(left); + self.z_axis = cross(at, self.y_axis); + } +} + +/// Shared-reference camera wrapper with the same axis mapping as the C# frame. +#[derive(Clone)] +pub struct AgentManagerAgentMovementAgentCamera { + pub far: f32, + frame: Arc>, +} + +impl fmt::Debug for AgentManagerAgentMovementAgentCamera { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("AgentCamera") + .field("far", &self.far) + .field("frame", &*mutex(&self.frame)) + .finish() + } +} + +impl AgentManagerAgentMovementAgentCamera { + pub fn new() -> Result { + Ok(Self { + far: 128.0, + frame: Arc::new(Mutex::new(CameraFrame::default())), + }) + } + + fn from_frame(frame: Arc>, far: f32) -> Self { + Self { far, frame } + } + + pub fn look_at_with_vector3_vector3( + &self, + position: Vector3, + target: Vector3, + ) -> Result<(), Error> { + self.look_at_with_vector3_vector3_vector3(position, target, Vector3::unit_z()) + } + + pub fn look_at_with_vector3_vector3_vector3( + &self, + position: Vector3, + target: Vector3, + up_direction: Vector3, + ) -> Result<(), Error> { + if !CameraFrame::finite(position) + || !CameraFrame::finite(target) + || !CameraFrame::finite(up_direction) + { + return Err(Error::Argument); + } + let mut frame = mutex(&self.frame); + frame.origin = position; + frame.look_direction(normalize(subtract(target, position)), up_direction); + Ok(()) + } + + pub fn look_direction_with_vector3(&self, target: Vector3) -> Result<(), Error> { + self.look_direction_with_vector3_vector3(target, Vector3::unit_z()) + } + + pub fn look_direction_with_vector3_vector3( + &self, + target: Vector3, + up_direction: Vector3, + ) -> Result<(), Error> { + if !CameraFrame::finite(target) || !CameraFrame::finite(up_direction) { + return Err(Error::Argument); + } + mutex(&self.frame).look_direction(target, up_direction); + Ok(()) + } + + pub fn look_direction_with_double(&self, heading: f64) -> Result<(), Error> { + if !heading.is_finite() { + return Err(Error::Argument); + } + let mut frame = mutex(&self.frame); + frame.y_axis = Vector3 { + x: heading.cos() as f32, + y: heading.sin() as f32, + z: frame.y_axis.z, + }; + frame.x_axis = Vector3 { + x: -heading.sin() as f32, + y: heading.cos() as f32, + z: frame.x_axis.z, + }; + Ok(()) + } + + pub fn pitch(&self, angle: f32) -> Result<(), Error> { + if !angle.is_finite() { + return Err(Error::Argument); + } + let mut frame = mutex(&self.frame); + let axis = frame.y_axis; + frame.rotate(angle, axis); + Ok(()) + } + + pub fn roll(&self, angle: f32) -> Result<(), Error> { + if !angle.is_finite() { + return Err(Error::Argument); + } + let mut frame = mutex(&self.frame); + let axis = frame.x_axis; + frame.rotate(angle, axis); + Ok(()) + } + + pub fn set_position_orientation( + &self, + position: Vector3, + roll: f32, + pitch: f32, + yaw: f32, + ) -> Result<(), Error> { + if !CameraFrame::finite(position) + || !roll.is_finite() + || !pitch.is_finite() + || !yaw.is_finite() + { + return Err(Error::Argument); + } + let mut frame = mutex(&self.frame); + frame.origin = position; + frame.x_axis = Vector3::unit_x(); + frame.y_axis = Vector3::unit_y(); + frame.z_axis = Vector3::unit_z(); + let axis = frame.x_axis; + frame.rotate(roll, axis); + let axis = frame.y_axis; + frame.rotate(pitch, axis); + let axis = frame.z_axis; + frame.rotate(yaw, axis); + Ok(()) + } + + pub fn yaw(&self, angle: f32) -> Result<(), Error> { + if !angle.is_finite() { + return Err(Error::Argument); + } + let mut frame = mutex(&self.frame); + let axis = frame.z_axis; + frame.rotate(angle, axis); + Ok(()) + } + + #[must_use] + pub fn at_axis(&self) -> Vector3 { + mutex(&self.frame).y_axis + } + + pub fn set_at_axis(&mut self, value: Vector3) { + if CameraFrame::finite(value) { + mutex(&self.frame).y_axis = value; + } + } + + #[must_use] + pub fn left_axis(&self) -> Vector3 { + mutex(&self.frame).x_axis + } + + pub fn set_left_axis(&mut self, value: Vector3) { + if CameraFrame::finite(value) { + mutex(&self.frame).x_axis = value; + } + } + + #[must_use] + pub fn position(&self) -> Vector3 { + mutex(&self.frame).origin + } + + pub fn set_position(&mut self, value: Vector3) { + if CameraFrame::finite(value) { + mutex(&self.frame).origin = value; + } + } + + #[must_use] + pub fn up_axis(&self) -> Vector3 { + mutex(&self.frame).z_axis + } + + pub fn set_up_axis(&mut self, value: Vector3) { + if CameraFrame::finite(value) { + mutex(&self.frame).z_axis = value; + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct WireState { + body_rotation: Quaternion, + head_rotation: Quaternion, + camera_center: Vector3, + camera_x_axis: Vector3, + camera_y_axis: Vector3, + camera_z_axis: Vector3, + far: f32, + flags: AgentFlags, + state: AgentState, +} + +impl Default for WireState { + fn default() -> Self { + Self { + body_rotation: Quaternion::identity(), + head_rotation: Quaternion::identity(), + camera_center: CameraFrame::default().origin, + camera_x_axis: Vector3::unit_x(), + camera_y_axis: Vector3::unit_y(), + camera_z_axis: Vector3::unit_z(), + far: 128.0, + flags: AgentFlags::NONE, + state: AgentState::NONE, + } + } +} + +#[derive(Clone, Copy, Default)] +struct LastUpdate { + wire: WireState, + duplicate_count: u8, +} + +#[derive(Clone, Copy, Debug)] +struct Kinematics { + relative_position: Vector3, + relative_rotation: Quaternion, + velocity: Vector3, + acceleration: Vector3, + angular_velocity: Vector3, + sitting_on: u32, + last_position_update: SystemTime, + home_position: Vector3, +} + +impl Default for Kinematics { + fn default() -> Self { + Self { + relative_position: Vector3::zero(), + relative_rotation: Quaternion::identity(), + velocity: Vector3::zero(), + acceleration: Vector3::zero(), + angular_velocity: Vector3::zero(), + sitting_on: 0, + last_position_update: SystemTime::UNIX_EPOCH, + home_position: Vector3::zero(), + } + } +} + +struct TeleportState { + status: TeleportStatus, + message: String, + generation: u64, + waiter: Option<(u64, tokio::sync::oneshot::Sender)>, +} + +impl Default for TeleportState { + fn default() -> Self { + Self { + status: TeleportStatus::None, + message: String::new(), + generation: 0, + waiter: None, + } + } +} + +#[derive(Default)] +struct TimerState { + connected: bool, + started: bool, + shutdown: bool, + revision: u64, +} + +#[derive(Default)] +struct TimerControl { + state: Mutex, + wake: Condvar, +} + +struct CrossingInfo { + state: AgentManagerCrossingState, + started: Instant, + old_simulator: Option, + new_simulator: Option, + region_handle: u64, + endpoint: SocketAddr, + seed: Uri, + position: Vector3, + look_at: Vector3, + size_x: u32, + size_y: u32, + retry_count: u8, + failure_reason: AgentManagerCrossingFailureReason, + failure_message: String, + generation: u64, + cancel_requested: bool, + restored_old_simulator: bool, +} + +#[allow(dead_code)] // Mirrors the reference's per-simulator cache for later packet updates. +#[derive(Clone, Copy)] +struct SimulatorAgentState { + position: Vector3, + rotation: Quaternion, + local_id: u32, + is_present: bool, + last_update: SystemTime, +} + +#[allow(dead_code)] // Tracks proactive child establishment exactly as the reference does. +#[derive(Clone, Copy)] +struct ChildAgentStatus { + request_time: SystemTime, + established: bool, + direction: BorderCrossingDirection, +} + +#[derive(Default)] +struct CrossingControl { + current: Mutex>, + wake: Condvar, +} + +pub(crate) struct AgentMovementRuntime { + client: Arc, + network: NetworkManager, + owner: Weak, + controls: AtomicU32, + always_run: AtomicBool, + auto_reset_controls: AtomicBool, + update_interval: AtomicI32, + camera_frame: Arc>, + last_update: Mutex, + kinematics: RwLock, + teleport: Mutex, + teleport_progress: EventRegistry, + camera_constraint: EventRegistry, + avatar_sit_response: EventRegistry, + region_crossed: EventRegistry, + region_crossing_predicted: EventRegistry, + timer: Arc, + timer_thread: Mutex>>, + lifecycle_subscriptions: Mutex>, + crossing_generation: AtomicU64, + crossing: Arc, + crossing_thread: Mutex>>, + simulator_states: RwLock>, + child_agent_status: RwLock>, + object_simulators: RwLock>>, +} + +impl fmt::Debug for AgentMovementRuntime { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("AgentMovementRuntime") + .field("controls", &self.controls.load(Ordering::Acquire)) + .field( + "update_interval", + &self.update_interval.load(Ordering::Acquire), + ) + .field("kinematics", &*read(&self.kinematics)) + .finish_non_exhaustive() + } +} + +impl Drop for AgentMovementRuntime { + fn drop(&mut self) { + { + let mut state = mutex(&self.timer.state); + state.shutdown = true; + state.revision = state.revision.wrapping_add(1); + } + self.timer.wake.notify_all(); + self.crossing_generation.fetch_add(1, Ordering::AcqRel); + self.crossing.wake.notify_all(); + if let Some(handle) = mutex(&self.timer_thread).take() + && handle.thread().id() != thread::current().id() + { + let _ = handle.join(); + } + if let Some(handle) = mutex(&self.crossing_thread).take() + && handle.thread().id() != thread::current().id() + { + let _ = handle.join(); + } + } +} + +impl AgentMovementRuntime { + pub(crate) fn new( + client: Arc, + network: NetworkManager, + owner: Weak, + ) -> Arc { + let interval = client.settings_ref().timing.agent_update_interval; + let runtime = Arc::new(Self { + client, + network, + owner, + controls: AtomicU32::new(0), + always_run: AtomicBool::new(false), + auto_reset_controls: AtomicBool::new(false), + update_interval: AtomicI32::new(interval), + camera_frame: Arc::new(Mutex::new(CameraFrame::default())), + last_update: Mutex::new(LastUpdate::default()), + kinematics: RwLock::new(Kinematics::default()), + teleport: Mutex::new(TeleportState::default()), + teleport_progress: EventRegistry::default(), + camera_constraint: EventRegistry::default(), + avatar_sit_response: EventRegistry::default(), + region_crossed: EventRegistry::default(), + region_crossing_predicted: EventRegistry::default(), + timer: Arc::new(TimerControl { + state: Mutex::new(TimerState { + connected: false, + started: false, + shutdown: false, + revision: 0, + }), + wake: Condvar::new(), + }), + timer_thread: Mutex::new(None), + lifecycle_subscriptions: Mutex::new(Vec::new()), + crossing_generation: AtomicU64::new(0), + crossing: Arc::new(CrossingControl::default()), + crossing_thread: Mutex::new(None), + simulator_states: RwLock::new(HashMap::new()), + child_agent_status: RwLock::new(HashMap::new()), + object_simulators: RwLock::new(HashMap::new()), + }); + runtime.install_lifecycle(); + runtime.start_timer_worker(); + runtime + } + + fn install_lifecycle(self: &Arc) { + let weak = Arc::downgrade(self); + let login = self + .network + .native_subscribe_login_progress(Arc::new(move |event| { + if let Some(runtime) = weak.upgrade() + && event.status() == crate::LoginStatus::Success + { + runtime.set_timer_connection(true, true); + } + })); + let weak = Arc::downgrade(self); + let disconnected = self + .network + .native_subscribe_disconnected(Arc::new(move |_| { + if let Some(runtime) = weak.upgrade() { + runtime.set_timer_connection(false, false); + } + })); + mutex(&self.lifecycle_subscriptions).extend([login, disconnected]); + } + + fn set_timer_connection(&self, connected: bool, login_succeeded: bool) { + let mut state = mutex(&self.timer.state); + state.connected = connected; + if login_succeeded { + state.started = self.client.settings_ref().agent.send_updates_regularly; + } + state.revision = state.revision.wrapping_add(1); + drop(state); + self.timer.wake.notify_all(); + } + + fn start_timer_worker(self: &Arc) { + let weak = Arc::downgrade(self); + let timer = Arc::clone(&self.timer); + let handle = thread::Builder::new() + .name("libremetaverse-agent-update".into()) + .spawn(move || { + loop { + let Some(runtime) = weak.upgrade() else { + break; + }; + let interval = runtime.update_interval.load(Ordering::Acquire); + drop(runtime); + + let state = mutex(&timer.state); + if state.shutdown { + break; + } + if !state.connected || !state.started || interval <= 0 { + drop( + timer + .wake + .wait(state) + .unwrap_or_else(std::sync::PoisonError::into_inner), + ); + continue; + } + let revision = state.revision; + let (next_state, timed_out) = timer + .wake + .wait_timeout( + state, + Duration::from_millis(u64::try_from(interval).unwrap_or(1)), + ) + .unwrap_or_else(std::sync::PoisonError::into_inner); + let should_send = timed_out.timed_out() + && !next_state.shutdown + && next_state.connected + && next_state.started + && next_state.revision == revision; + drop(next_state); + if should_send + && let Some(runtime) = weak.upgrade() + && runtime.client.settings_ref().agent.send_updates + && runtime.network.native_connected() + { + let _ = runtime.send_last_update(false, None); + } + } + }); + if let Ok(handle) = handle { + *mutex(&self.timer_thread) = Some(handle); + } + } + + pub(crate) fn stop(&self) { + { + let mut state = mutex(&self.timer.state); + state.connected = false; + state.started = false; + state.shutdown = true; + state.revision = state.revision.wrapping_add(1); + } + self.timer.wake.notify_all(); + if let Some(handle) = mutex(&self.timer_thread).take() + && handle.thread().id() != thread::current().id() + { + let _ = handle.join(); + } + let mut teleport = mutex(&self.teleport); + if let Some((_, waiter)) = teleport.waiter.take() { + let _ = waiter.send(false); + } + drop(teleport); + self.crossing_generation.fetch_add(1, Ordering::AcqRel); + if let Some(crossing) = mutex(&self.crossing.current).as_mut() { + crossing.cancel_requested = true; + } + self.crossing.wake.notify_all(); + if let Some(handle) = mutex(&self.crossing_thread).take() + && handle.thread().id() != thread::current().id() + { + let _ = handle.join(); + } + } + + fn ids(&self) -> (UUID, UUID) { + self.owner.upgrade().map_or_else( + || { + ( + self.network.native_agent_id(), + self.network.native_session_id(), + ) + }, + |owner| owner.native_ids(&self.network), + ) + } + + fn current_sim(&self) -> Result { + self.network + .native_current_sim() + .ok_or(Error::InvalidOperation) + } + + fn send_current( + &self, + packet_type: PacketType, + data: Vec, + reliable: Option, + ) -> Result<(), Error> { + send_encoded(&self.current_sim()?, packet_type, data, reliable) + } + + fn snapshot(&self) -> WireState { + mutex(&self.last_update).wire + } + + fn camera(&self, far: f32) -> AgentManagerAgentMovementAgentCamera { + AgentManagerAgentMovementAgentCamera::from_frame(Arc::clone(&self.camera_frame), far) + } + + fn send_update( + &self, + wire: WireState, + reliable: bool, + simulator: Option, + ) -> Result<(), Error> { + let simulator = simulator.map_or_else(|| self.current_sim(), Ok)?; + if !simulator.native_agent_movement_complete() { + return Ok(()); + } + let controls = self.controls.load(Ordering::Acquire); + let should_send = { + let mut last = mutex(&self.last_update); + if controls == 0 && wire == last.wire { + last.duplicate_count = last.duplicate_count.saturating_add(1); + } else { + last.duplicate_count = 0; + } + let should_send = self + .client + .settings_ref() + .agent + .disable_update_duplicate_check + || last.duplicate_count < 10; + if should_send { + last.wire = wire; + } + should_send + }; + if !should_send { + return Ok(()); + } + + let (agent_id, session_id) = self.ids(); + let mut packet = AgentUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.head_rotation = wire.head_rotation; + packet.agent_data.body_rotation = wire.body_rotation; + // Preserve the reference's deliberate Camera wrapper/frame swap. + packet.agent_data.camera_at_axis = wire.camera_x_axis; + packet.agent_data.camera_center = wire.camera_center; + packet.agent_data.camera_left_axis = wire.camera_y_axis; + packet.agent_data.camera_up_axis = wire.camera_z_axis; + packet.agent_data.far = wire.far; + packet.agent_data.state = wire.state.0; + packet.agent_data.control_flags = controls; + packet.agent_data.flags = wire.flags.0; + send_encoded( + &simulator, + PacketType::AgentUpdate, + packet.to_bytes_with_method()?, + Some(reliable), + )?; + if self.auto_reset_controls.load(Ordering::Acquire) { + self.reset_control_flags(); + } + Ok(()) + } + + fn send_last_update(&self, reliable: bool, simulator: Option) -> Result<(), Error> { + self.send_update(self.snapshot(), reliable, simulator) + } + + fn reset_control_flags(&self) { + const PERSISTENT: u32 = (1 << 27) | (1 << 13) | (1 << 18) | (1 << 5); + self.controls.fetch_and(PERSISTENT, Ordering::AcqRel); + } + + fn set_flag(&self, flag: u32, value: bool) { + if value { + self.controls.fetch_or(flag, Ordering::AcqRel); + } else { + self.controls.fetch_and(!flag, Ordering::AcqRel); + } + } + + fn flag(&self, flag: u32) -> bool { + self.controls.load(Ordering::Acquire) & flag != 0 + } + + fn set_update_interval(&self, value: i32) { + self.update_interval.store(value.max(0), Ordering::Release); + let mut state = mutex(&self.timer.state); + state.revision = state.revision.wrapping_add(1); + drop(state); + self.timer.wake.notify_all(); + } + + fn update_position(&self, position: Vector3, look_at: Vector3) -> Result<(), Error> { + { + let mut kinematics = write(&self.kinematics); + kinematics.relative_position = position; + kinematics.last_position_update = SystemTime::now(); + } + self.camera(128.0).look_direction_with_vector3(look_at) + } + + fn teleport_event(&self, message: String, status: TeleportStatus, flags: TeleportFlags) { + let terminal = matches!( + status, + TeleportStatus::Failed | TeleportStatus::Finished | TeleportStatus::Cancelled + ); + let waiter = { + let mut state = mutex(&self.teleport); + state.message.clone_from(&message); + state.status = status; + if terminal { + state.waiter.take().map(|(_, waiter)| waiter) + } else { + None + } + }; + self.teleport_progress.emit(TeleportEventArgs { + message, + status, + flags, + }); + if let Some(waiter) = waiter { + let _ = waiter.send(status == TeleportStatus::Finished); + } + } + + fn begin_teleport(&self) -> (u64, tokio::sync::oneshot::Receiver) { + let (sender, receiver) = tokio::sync::oneshot::channel(); + let old_waiter = { + let mut state = mutex(&self.teleport); + state.generation = state.generation.wrapping_add(1); + state.status = TeleportStatus::None; + state.message.clear(); + let generation = state.generation; + let old = state.waiter.replace((generation, sender)); + (generation, old) + }; + if let Some((_, old)) = old_waiter.1 { + let _ = old.send(false); + } + (old_waiter.0, receiver) + } + + async fn wait_for_teleport( + &self, + generation: u64, + receiver: tokio::sync::oneshot::Receiver, + cancellation_token: Option, + ) -> Result { + let cancellation = cancellation_token.unwrap_or_default(); + if let Err(error) = cancellation.throw_if_cancellation_requested() { + let mut state = mutex(&self.teleport); + if state + .waiter + .as_ref() + .is_some_and(|(candidate, _)| *candidate == generation) + { + state.waiter.take(); + } + return Err(error); + } + let timeout = Duration::from_millis( + u64::try_from(self.client.settings_ref().timing.teleport_timeout.max(1)).unwrap_or(1), + ); + tokio::select! { + result = receiver => Ok(result.unwrap_or(false)), + () = cancellation.cancelled() => { + let mut state = mutex(&self.teleport); + if state.waiter.as_ref().is_some_and(|(candidate, _)| *candidate == generation) { + state.waiter.take(); + } + Err(Error::Cancelled) + } + () = tokio::time::sleep(timeout) => { + let mut state = mutex(&self.teleport); + if state.waiter.as_ref().is_some_and(|(candidate, _)| *candidate == generation) { + state.waiter.take(); + state.message = "Teleport timed out.".into(); + state.status = TeleportStatus::Failed; + } + Ok(false) + } + } + } + + async fn wait_for_event_queue( + &self, + cancellation_token: Option, + ) -> Result<(), Error> { + let simulator = self.current_sim()?; + if simulator.native_is_event_queue_running(None)? { + return Ok(()); + } + simulator.native_start_event_queue(); + let cancellation = cancellation_token.unwrap_or_default(); + cancellation.throw_if_cancellation_requested()?; + let (sender, receiver) = tokio::sync::oneshot::channel(); + let sender = Mutex::new(Some(sender)); + let expected = simulator.clone(); + let subscription = + self.network + .native_subscribe_event_queue_running(Arc::new(move |event| { + if event.simulator() == expected + && let Some(sender) = mutex(&sender).take() + { + let _ = sender.send(()); + } + })); + tokio::select! { + _ = receiver => {} + () = cancellation.cancelled() => { + drop(subscription); + return Err(Error::Cancelled); + } + () = tokio::time::sleep(Duration::from_secs(10)) => {} + } + drop(subscription); + Ok(()) + } + + fn request_teleport_landmark(&self, landmark: UUID) -> Result<(), Error> { + let (agent_id, session_id) = self.ids(); + let mut packet = TeleportLandmarkRequestPacket::new_with_constructor()?; + packet.info.agent_id = agent_id; + packet.info.session_id = session_id; + packet.info.landmark_id = landmark; + self.send_current( + PacketType::TeleportLandmarkRequest, + packet.to_bytes_with_method()?, + None, + ) + } + + fn request_teleport_location( + &self, + region_handle: u64, + position: Vector3, + look_at: Vector3, + ignore_caps_status: bool, + ) -> Result<(), Error> { + let sim = self.current_sim()?; + if !ignore_caps_status && !sim.native_is_event_queue_running(None)? { + self.teleport_event( + "CAPS event queue is not running".into(), + TeleportStatus::Failed, + TeleportFlags::DEFAULT, + ); + return Ok(()); + } + let (agent_id, session_id) = self.ids(); + let mut packet = TeleportLocationRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.info.look_at = look_at; + packet.info.position = position; + packet.info.region_handle = region_handle; + send_encoded( + &sim, + PacketType::TeleportLocationRequest, + packet.to_bytes_with_method()?, + None, + ) + } + + fn complete_agent_movement(&self, simulator: Simulator) -> Result<(), Error> { + let (agent_id, session_id) = self.ids(); + let mut packet = crate::packets::CompleteAgentMovementPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.circuit_code = self.network.native_circuit_code(); + send_encoded( + &simulator, + PacketType::CompleteAgentMovement, + packet.to_bytes_with_method()?, + None, + ) + } + + fn connect_destination( + &self, + endpoint: SocketAddr, + region_handle: u64, + seed: Option, + size_x: u32, + size_y: u32, + ) -> bool { + if let Some(old) = self.network.native_current_sim() { + old.native_set_agent_movement_complete(false); + } + self.network + .native_connect(endpoint, region_handle, true, seed, size_x, size_y) + .ok() + .flatten() + .is_some() + } + + #[allow(clippy::too_many_lines)] // Packet dispatch mirrors the protocol state machine. + pub(crate) fn handle_raw_packet( + self: &Arc, + packet_type: PacketType, + data: &[u8], + simulator: Simulator, + ) -> Result { + match packet_type { + PacketType::AgentMovementComplete => { + let packet = decode_packet::(data)?; + self.update_position(packet.data.position, packet.data.look_at)?; + simulator.native_set_agent_movement_complete(true); + self.notify_movement_complete(&simulator); + self.update_multi_simulator_state(&simulator); + Ok(true) + } + PacketType::CameraConstraint => { + let packet = decode_packet::(data)?; + self.camera_constraint.emit(CameraConstraintEventArgs { + collide_plane: packet.camera_collide_plane.plane, + }); + Ok(true) + } + PacketType::AvatarSitResponse => { + let packet = decode_packet::(data)?; + let transform = packet.sit_transform; + self.avatar_sit_response.emit(AvatarSitResponseEventArgs { + object_id: packet.sit_object.id, + auto_pilot: transform.auto_pilot, + camera_at_offset: transform.camera_at_offset, + camera_eye_offset: transform.camera_eye_offset, + force_mouselook: transform.force_mouselook, + sit_position: transform.sit_position, + sit_rotation: transform.sit_rotation, + }); + Ok(true) + } + PacketType::TeleportStart => { + let packet = decode_packet::(data)?; + self.teleport_event( + "Teleport started".into(), + TeleportStatus::Start, + TeleportFlags(packet.info.teleport_flags), + ); + Ok(true) + } + PacketType::TeleportProgress => { + let packet = decode_packet::(data)?; + self.teleport_event( + wire_string(packet.info.message), + TeleportStatus::Progress, + TeleportFlags(packet.info.teleport_flags), + ); + Ok(true) + } + PacketType::TeleportFailed => { + let packet = decode_packet::(data)?; + let status = mutex(&self.teleport).status; + if matches!(status, TeleportStatus::Finished | TeleportStatus::None) { + return Ok(true); + } + self.teleport_event( + wire_string(packet.info.reason), + TeleportStatus::Failed, + TeleportFlags::DEFAULT, + ); + Ok(true) + } + PacketType::TeleportCancel => { + let _ = decode_packet::(data)?; + self.teleport_event( + "Cancelled".into(), + TeleportStatus::Cancelled, + TeleportFlags::DEFAULT, + ); + Ok(true) + } + PacketType::TeleportLocal => { + let packet = decode_packet::(data)?; + self.update_position(packet.info.position, packet.info.look_at)?; + self.teleport_event( + "Teleport finished".into(), + TeleportStatus::Finished, + TeleportFlags(packet.info.teleport_flags), + ); + Ok(true) + } + PacketType::TeleportFinish => { + let packet = decode_packet::(data)?; + let endpoint = SocketAddr::new( + IpAddr::V4(Ipv4Addr::from(packet.info.sim_ip.to_le_bytes())), + packet.info.sim_port, + ); + let seed = Uri(wire_string(packet.info.seed_capability)); + let success = self.connect_destination( + endpoint, + packet.info.region_handle, + Some(seed), + 256, + 256, + ); + self.teleport_event( + if success { + "Teleport finished".into() + } else { + "Failed to connect to simulator after teleport".into() + }, + if success { + TeleportStatus::Finished + } else { + TeleportStatus::Failed + }, + TeleportFlags(packet.info.teleport_flags), + ); + Ok(true) + } + PacketType::CrossedRegion => { + let packet = decode_packet::(data)?; + self.begin_crossing( + simulator, + SocketAddr::new( + IpAddr::V4(Ipv4Addr::from(packet.region_data.sim_ip.to_le_bytes())), + packet.region_data.sim_port, + ), + packet.region_data.region_handle, + Uri(wire_string(packet.region_data.seed_capability)), + packet.info.position, + packet.info.look_at, + 256, + 256, + ); + Ok(true) + } + _ => Ok(false), + } + } + + #[allow(clippy::too_many_arguments)] + fn begin_crossing( + self: &Arc, + old_simulator: Simulator, + endpoint: SocketAddr, + region_handle: u64, + seed: Uri, + position: Vector3, + look_at: Vector3, + size_x: u32, + size_y: u32, + ) -> bool { + { + let current = mutex(&self.crossing.current); + if current.as_ref().is_some_and(|crossing| { + !matches!( + crossing.state, + AgentManagerCrossingState::Idle + | AgentManagerCrossingState::Completed + | AgentManagerCrossingState::Failed + ) + }) { + return false; + } + } + let generation = self.crossing_generation.fetch_add(1, Ordering::AcqRel) + 1; + *mutex(&self.crossing.current) = Some(CrossingInfo { + state: AgentManagerCrossingState::PreparingCross, + started: Instant::now(), + old_simulator: Some(old_simulator), + new_simulator: None, + region_handle, + endpoint, + seed, + position, + look_at, + size_x, + size_y, + retry_count: 0, + failure_reason: AgentManagerCrossingFailureReason::Unknown, + failure_message: String::new(), + generation, + cancel_requested: false, + restored_old_simulator: false, + }); + let weak = Arc::downgrade(self); + let crossing = Arc::clone(&self.crossing); + let spawned = thread::Builder::new() + .name("libremetaverse-region-crossing".into()) + .spawn(move || Self::run_crossing(weak, crossing, generation)); + let Ok(handle) = spawned else { + if let Some(info) = mutex(&self.crossing.current).as_mut() { + info.state = AgentManagerCrossingState::Failed; + info.failure_reason = AgentManagerCrossingFailureReason::NetworkError; + info.failure_message = "Unable to start region crossing worker".into(); + } + return false; + }; + if let Some(previous) = mutex(&self.crossing_thread).replace(handle) + && previous.thread().id() != thread::current().id() + { + let _ = previous.join(); + } + true + } + + #[allow(clippy::too_many_lines)] // Keeping transitions together makes the state audit explicit. + fn run_crossing(weak: Weak, crossing: Arc, generation: u64) { + loop { + let (endpoint, region_handle, seed, size_x, size_y) = { + let mut current = mutex(&crossing.current); + let Some(info) = current + .as_mut() + .filter(|info| info.generation == generation) + else { + return; + }; + if info.cancel_requested { + drop(current); + Self::recover_crossing(&weak, &crossing, generation); + return; + } + info.state = AgentManagerCrossingState::Connecting; + ( + info.endpoint, + info.region_handle, + info.seed.clone(), + info.size_x, + info.size_y, + ) + }; + + let Some(runtime) = weak.upgrade() else { + return; + }; + if runtime.crossing_generation.load(Ordering::Acquire) != generation { + return; + } + let connection = runtime.network.native_connect( + endpoint, + region_handle, + true, + Some(seed), + size_x, + size_y, + ); + drop(runtime); + + match connection { + Ok(Some(new_simulator)) => { + let (old_simulator, position, look_at) = { + let mut current = mutex(&crossing.current); + let Some(info) = current + .as_mut() + .filter(|info| info.generation == generation) + else { + return; + }; + if info.cancel_requested { + info.new_simulator = Some(new_simulator); + drop(current); + Self::recover_crossing(&weak, &crossing, generation); + return; + } + info.new_simulator = Some(new_simulator.clone()); + info.state = AgentManagerCrossingState::WaitingForComplete; + (info.old_simulator.clone(), info.position, info.look_at) + }; + crossing.wake.notify_all(); + if let Some(old) = &old_simulator + && *old != new_simulator + { + old.native_set_agent_movement_complete(false); + } + if let Some(runtime) = weak.upgrade() { + let _ = runtime.update_position(position, look_at); + } + + let current = mutex(&crossing.current); + let (mut current, timeout) = crossing + .wake + .wait_timeout_while(current, Duration::from_secs(30), |current| { + current.as_ref().is_some_and(|info| { + info.generation == generation + && info.state == AgentManagerCrossingState::WaitingForComplete + && !info.cancel_requested + }) + }) + .unwrap_or_else(std::sync::PoisonError::into_inner); + let Some(info) = current + .as_mut() + .filter(|info| info.generation == generation) + else { + return; + }; + if info.state == AgentManagerCrossingState::Completed { + let event = RegionCrossedEventArgs { + old_simulator: info.old_simulator.clone(), + new_simulator: info.new_simulator.clone(), + }; + info.state = AgentManagerCrossingState::Idle; + drop(current); + if let Some(runtime) = weak.upgrade() { + runtime.region_crossed.emit(event); + } + return; + } + if timeout.timed_out() { + info.failure_reason = AgentManagerCrossingFailureReason::Timeout; + info.failure_message = "Timeout in state WaitingForComplete".into(); + } + drop(current); + Self::recover_crossing(&weak, &crossing, generation); + return; + } + Ok(None) => { + let mut current = mutex(&crossing.current); + let Some(info) = current + .as_mut() + .filter(|info| info.generation == generation) + else { + return; + }; + info.failure_reason = AgentManagerCrossingFailureReason::ConnectionFailed; + info.failure_message = format!("Failed to connect to {}", info.endpoint); + if info.retry_count >= 3 { + info.failure_reason = AgentManagerCrossingFailureReason::MaxRetriesExceeded; + info.failure_message.push_str(" after 3 retries"); + drop(current); + Self::recover_crossing(&weak, &crossing, generation); + return; + } + info.retry_count += 1; + let delay = Duration::from_secs(u64::from(info.retry_count)); + let (current, _) = crossing + .wake + .wait_timeout(current, delay) + .unwrap_or_else(std::sync::PoisonError::into_inner); + if current + .as_ref() + .is_none_or(|info| info.generation != generation || info.cancel_requested) + { + drop(current); + Self::recover_crossing(&weak, &crossing, generation); + return; + } + } + Err(error) => { + let mut current = mutex(&crossing.current); + if let Some(info) = current + .as_mut() + .filter(|info| info.generation == generation) + { + info.failure_reason = AgentManagerCrossingFailureReason::NetworkError; + info.failure_message = format!("Exception during connection: {error}"); + } + drop(current); + Self::recover_crossing(&weak, &crossing, generation); + return; + } + } + } + } + + fn recover_crossing(weak: &Weak, crossing: &CrossingControl, generation: u64) { + let (old_simulator, new_simulator) = { + let mut current = mutex(&crossing.current); + let Some(info) = current + .as_mut() + .filter(|info| info.generation == generation) + else { + return; + }; + info.state = AgentManagerCrossingState::Recovering; + info.restored_old_simulator = info.old_simulator.is_some(); + (info.old_simulator.clone(), info.new_simulator.clone()) + }; + if let Some(runtime) = weak.upgrade() { + if runtime.crossing_generation.load(Ordering::Acquire) != generation { + return; + } + if let Some(old) = &old_simulator { + old.native_set_agent_movement_complete(true); + runtime.network.native_set_current_sim(Some(old.clone())); + let _ = runtime.complete_agent_movement(old.clone()); + } + if let Some(new_simulator) = new_simulator { + let _ = runtime.network.native_disconnect_sim(new_simulator, false); + } + let event = RegionCrossedEventArgs { + old_simulator, + new_simulator: None, + }; + if let Some(info) = mutex(&crossing.current) + .as_mut() + .filter(|info| info.generation == generation) + { + info.state = AgentManagerCrossingState::Failed; + info.state = AgentManagerCrossingState::Idle; + } + runtime.region_crossed.emit(event); + } + } + + fn notify_movement_complete(&self, simulator: &Simulator) { + let mut current = mutex(&self.crossing.current); + let Some(info) = current.as_mut() else { + return; + }; + if info.state != AgentManagerCrossingState::WaitingForComplete { + return; + } + if info.new_simulator.as_ref() == Some(simulator) { + info.state = AgentManagerCrossingState::Completed; + } else if info.old_simulator.as_ref() == Some(simulator) { + info.failure_reason = AgentManagerCrossingFailureReason::SimulatorRejected; + info.failure_message = + "Received MovementComplete from old simulator instead of new one".into(); + info.cancel_requested = true; + } else { + return; + } + drop(current); + self.crossing.wake.notify_all(); + } + + fn update_multi_simulator_state(&self, simulator: &Simulator) { + let state = *read(&self.kinematics); + write(&self.simulator_states).insert( + simulator.native_ip_end_point(), + SimulatorAgentState { + position: state.relative_position, + rotation: state.relative_rotation, + local_id: 0, + is_present: simulator.native_agent_movement_complete(), + last_update: SystemTime::now(), + }, + ); + if !self.client.settings_ref().agent.multiple_sims { + return; + } + self.predict_crossing(simulator, state.relative_position, state.velocity); + self.establish_neighbor_children(simulator, state.relative_position, state.velocity); + self.cleanup_object_tracking(); + let now = SystemTime::now(); + write(&self.child_agent_status).retain(|_, status| { + now.duration_since(status.request_time).unwrap_or_default() <= Duration::from_mins(2) + }); + } + + #[allow(clippy::float_cmp)] // Golden code selects the direction by exact minimum equality. + fn predict_crossing(&self, simulator: &Simulator, position: Vector3, velocity: Vector3) { + if velocity == Vector3::zero() { + return; + } + let west = if velocity.x < -0.1 { + -position.x / velocity.x + } else { + f32::MAX + }; + let east = if velocity.x > 0.1 { + (simulator.size_x as f32 - position.x) / velocity.x + } else { + f32::MAX + }; + let south = if velocity.y < -0.1 { + -position.y / velocity.y + } else { + f32::MAX + }; + let north = if velocity.y > 0.1 { + (simulator.size_y as f32 - position.y) / velocity.y + } else { + f32::MAX + }; + let time = west.min(east).min(south).min(north); + if !(0.0 < time && time < 3.0) { + return; + } + let direction = if time == west { + BorderCrossingDirection::West + } else if time == east { + BorderCrossingDirection::East + } else if time == south { + BorderCrossingDirection::South + } else if time == north { + BorderCrossingDirection::North + } else { + BorderCrossingDirection::Unknown + }; + self.region_crossing_predicted + .emit(RegionCrossingPredictionEventArgs { + current_simulator: simulator.clone(), + direction, + time_until_crossing: time, + }); + } + + fn establish_neighbor_children( + &self, + simulator: &Simulator, + position: Vector3, + velocity: Vector3, + ) { + if !simulator.native_agent_movement_complete() { + return; + } + let x = u32::try_from(simulator.handle >> 32).unwrap_or_default(); + let y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default(); + let size_x = simulator.size_x as f32; + let size_y = simulator.size_y as f32; + if velocity.x < -0.5 && position.x < 128.0 { + self.establish_child(x.wrapping_sub(256), y, BorderCrossingDirection::West); + } else if velocity.x > 0.5 && position.x > size_x - 128.0 { + self.establish_child(x.wrapping_add(256), y, BorderCrossingDirection::East); + } + if velocity.y < -0.5 && position.y < 128.0 { + self.establish_child(x, y.wrapping_sub(256), BorderCrossingDirection::South); + } else if velocity.y > 0.5 && position.y > size_y - 128.0 { + self.establish_child(x, y.wrapping_add(256), BorderCrossingDirection::North); + } + if Self::is_near_corner(position, size_x, size_y, 64.0) { + for (child_x, child_y, direction) in [ + (x.wrapping_sub(256), y, BorderCrossingDirection::West), + (x.wrapping_add(256), y, BorderCrossingDirection::East), + (x, y.wrapping_sub(256), BorderCrossingDirection::South), + (x, y.wrapping_add(256), BorderCrossingDirection::North), + ] { + self.establish_child(child_x, child_y, direction); + } + } + } + + fn establish_child(&self, x: u32, y: u32, direction: BorderCrossingDirection) { + let handle = (u64::from(x) << 32) | u64::from(y); + let now = SystemTime::now(); + let mut children = write(&self.child_agent_status); + if children.get(&handle).is_some_and(|status| { + now.duration_since(status.request_time).unwrap_or_default() < Duration::from_secs(30) + }) { + return; + } + let established = self + .network + .simulators + .snapshot() + .iter() + .any(|simulator| simulator.handle == handle); + children.insert( + handle, + ChildAgentStatus { + request_time: now, + established, + direction, + }, + ); + } + + const fn is_near_corner(position: Vector3, size_x: f32, size_y: f32, threshold: f32) -> bool { + (position.x < threshold || position.x > size_x - threshold) + && (position.y < threshold || position.y > size_y - threshold) + } + + fn cleanup_object_tracking(&self) { + write(&self.object_simulators).retain(|_, simulators| { + simulators.retain(Simulator::native_is_connected); + !simulators.is_empty() + }); + } + + fn track_object(&self, object_id: UUID, simulator: Simulator) { + if object_id == UUID::zero() { + return; + } + let mut objects = write(&self.object_simulators); + let simulators = objects.entry(object_id).or_default(); + if !simulators.contains(&simulator) { + simulators.push(simulator); + } + } + + fn untrack_object(&self, object_id: UUID, simulator: &Simulator) { + let mut objects = write(&self.object_simulators); + if let Some(simulators) = objects.get_mut(&object_id) { + simulators.retain(|candidate| candidate != simulator); + if simulators.is_empty() { + objects.remove(&object_id); + } + } + } + + fn object_simulators(&self, object_id: UUID) -> Vec { + read(&self.object_simulators) + .get(&object_id) + .cloned() + .unwrap_or_default() + } + + pub(crate) fn handle_caps_event( + self: &Arc, + name: &str, + message: &dyn IMessage, + simulator: Simulator, + ) -> Result { + match name { + "TeleportFinish" => { + let mut finish = crate::messages::linden::TeleportFinishMessage::new()?; + finish.deserialize(message.serialize()?)?; + let port = u16::try_from(finish.port).map_err(|_| Error::Argument)?; + let success = self.connect_destination( + SocketAddr::new(finish.ip, port), + finish.region_handle, + Some(finish.seed_capability), + finish.region_size_x, + finish.region_size_y, + ); + self.teleport_event( + if success { + "Teleport finished".into() + } else { + "Failed to connect to simulator after teleport".into() + }, + if success { + TeleportStatus::Finished + } else { + TeleportStatus::Failed + }, + finish.flags, + ); + Ok(true) + } + "TeleportFailed" => { + let mut failed = crate::messages::linden::TeleportFailedMessage::new()?; + failed.deserialize(message.serialize()?)?; + let status = mutex(&self.teleport).status; + if !matches!(status, TeleportStatus::Finished | TeleportStatus::None) { + self.teleport_event( + failed.reason, + TeleportStatus::Failed, + TeleportFlags::DEFAULT, + ); + } + Ok(true) + } + "CrossedRegion" => { + let mut crossed = crate::messages::linden::CrossedRegionMessage::new()?; + crossed.deserialize(message.serialize()?)?; + let port = u16::try_from(crossed.port).map_err(|_| Error::Argument)?; + self.begin_crossing( + simulator, + SocketAddr::new(crossed.ip, port), + crossed.region_handle, + crossed.seed_capability, + crossed.position, + crossed.look_at, + crossed.region_size_x, + crossed.region_size_y, + ); + Ok(true) + } + _ => Ok(false), + } + } + + pub(crate) fn sim_position(&self) -> Vector3 { + let state = *read(&self.kinematics); + if state.sitting_on == 0 { + return state.relative_position; + } + let Some(simulator) = self.network.native_current_sim() else { + return state.relative_position; + }; + let primitives = read(&simulator.objects_primitives); + let Some(parent) = primitives.get(&state.sitting_on) else { + return state.relative_position; + }; + add( + parent.position, + Vector3::mul_with_vector3_quaternion(state.relative_position, parent.rotation), + ) + } + + pub(crate) fn sim_rotation(&self) -> Quaternion { + let state = *read(&self.kinematics); + if state.sitting_on == 0 { + return state.relative_rotation; + } + self.network + .native_current_sim() + .and_then(|simulator| { + read(&simulator.objects_primitives) + .get(&state.sitting_on) + .map(|parent| { + Quaternion::mul_with_quaternion_quaternion( + state.relative_rotation, + parent.rotation, + ) + }) + }) + .unwrap_or(state.relative_rotation) + } +} + +/// Public movement handle. Control and camera state are shared with packet callbacks. +pub struct AgentManagerAgentMovement { + pub body_rotation: Quaternion, + pub camera: AgentManagerAgentMovementAgentCamera, + pub flags: AgentFlags, + pub head_rotation: Quaternion, + pub state: AgentState, + pub(crate) runtime: Arc, +} + +impl fmt::Debug for AgentManagerAgentMovement { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("AgentMovement") + .field("body_rotation", &self.body_rotation) + .field("camera", &self.camera) + .field("flags", &self.flags) + .field("head_rotation", &self.head_rotation) + .field("state", &self.state) + .field("controls", &self.agent_controls()) + .finish_non_exhaustive() + } +} + +impl AgentManagerAgentMovement { + pub(crate) fn from_runtime(runtime: Arc) -> Self { + let wire = runtime.snapshot(); + Self { + body_rotation: wire.body_rotation, + camera: runtime.camera(wire.far), + flags: wire.flags, + head_rotation: wire.head_rotation, + state: wire.state, + runtime, + } + } + + pub fn new(client: GridClient) -> Result { + Ok(AgentManager::native_new(Some(Arc::new(client)))?.movement) + } + + fn wire(&self) -> WireState { + WireState { + body_rotation: self.body_rotation, + head_rotation: self.head_rotation, + camera_center: self.camera.position(), + camera_x_axis: self.camera.left_axis(), + camera_y_axis: self.camera.at_axis(), + camera_z_axis: self.camera.up_axis(), + far: self.camera.far, + flags: self.flags, + state: self.state, + } + } + + pub fn reset_control_flags(&self) -> Result<(), Error> { + self.runtime.reset_control_flags(); + Ok(()) + } + + pub fn send_manual_update( + &self, + control_flags: AgentManagerControlFlags, + position: Vector3, + forward_axis: Vector3, + left_axis: Vector3, + up_axis: Vector3, + body_rotation: Quaternion, + head_rotation: Quaternion, + far_clip: f32, + flags: AgentFlags, + state: AgentState, + reliable: bool, + ) -> Result<(), Error> { + let simulator = self.runtime.current_sim()?; + if !simulator.native_handshake_complete() { + return Ok(()); + } + let (agent_id, session_id) = self.runtime.ids(); + let mut packet = AgentUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.body_rotation = body_rotation; + packet.agent_data.head_rotation = head_rotation; + packet.agent_data.camera_center = position; + packet.agent_data.camera_at_axis = forward_axis; + packet.agent_data.camera_left_axis = left_axis; + packet.agent_data.camera_up_axis = up_axis; + packet.agent_data.far = far_clip; + packet.agent_data.control_flags = control_flags.0.cast_unsigned(); + packet.agent_data.flags = flags.0; + packet.agent_data.state = state.0; + send_encoded( + &simulator, + PacketType::AgentUpdate, + packet.to_bytes_with_method()?, + Some(reliable), + ) + } + + pub fn send_update_with_boolean(&self, reliable: Option) -> Result<(), Error> { + self.runtime + .send_update(self.wire(), reliable.unwrap_or(false), None) + } + + pub fn send_update_with_boolean_simulator( + &self, + reliable: bool, + simulator: Simulator, + ) -> Result<(), Error> { + self.runtime + .send_update(self.wire(), reliable, Some(simulator)) + } + + pub fn set_fov_vertical_angle(&self, angle: f32) -> Result<(), Error> { + let (agent_id, session_id) = self.runtime.ids(); + let mut packet = AgentFOVPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.circuit_code = self.runtime.network.native_circuit_code(); + packet.fov_block.gen_counter = 0; + packet.fov_block.vertical_angle = angle; + self.runtime + .send_current(PacketType::AgentFOV, packet.to_bytes_with_method()?, None) + } + + pub fn turn_toward(&self, target: Vector3, send_update: Option) -> Result { + if !self.runtime.client.settings_ref().agent.send_updates { + return Ok(false); + } + let position = self.runtime.sim_position(); + let direction = normalize(subtract(target, position)); + if direction == Vector3::zero() { + return Ok(false); + } + let rotation = Vector3::rotation_between(Vector3::unit_x(), direction)?; + self.camera.look_at_with_vector3_vector3(position, target)?; + let mut wire = self.wire(); + wire.body_rotation = rotation; + wire.head_rotation = rotation; + if send_update.unwrap_or(true) { + self.runtime.send_update(wire, false, None)?; + } else { + mutex(&self.runtime.last_update).wire = wire; + } + Ok(true) + } + + pub fn update_from_heading(&self, heading: f64, reliable: bool) -> Result<(), Error> { + let position = self.runtime.sim_position(); + { + let mut camera = self.camera.clone(); + camera.set_position(position); + camera.look_direction_with_double(heading)?; + } + let rotation = Quaternion { + x: self.body_rotation.x, + y: self.body_rotation.y, + z: (heading / 2.0).sin() as f32, + w: (heading / 2.0).cos() as f32, + }; + let mut wire = self.wire(); + wire.body_rotation = rotation; + wire.head_rotation = rotation; + self.runtime.send_update(wire, reliable, None) + } + + #[must_use] + pub fn agent_controls(&self) -> u32 { + self.runtime.controls.load(Ordering::Acquire) + } + + pub fn set_agent_controls(&mut self, value: u32) { + self.runtime.controls.store(value, Ordering::Release); + } + + #[must_use] + pub fn always_run(&self) -> bool { + self.runtime.always_run.load(Ordering::Acquire) + } + + pub fn set_always_run(&mut self, value: bool) { + self.runtime.always_run.store(value, Ordering::Release); + let result = (|| { + let (agent_id, session_id) = self.runtime.ids(); + let mut packet = SetAlwaysRunPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.always_run = value; + self.runtime.send_current( + PacketType::SetAlwaysRun, + packet.to_bytes_with_method()?, + None, + ) + })(); + let _: Result<(), Error> = result; + } + + #[must_use] + pub fn auto_reset_controls(&self) -> bool { + self.runtime.auto_reset_controls.load(Ordering::Acquire) + } + + pub fn set_auto_reset_controls(&mut self, value: bool) { + self.runtime + .auto_reset_controls + .store(value, Ordering::Release); + } + + #[must_use] + pub fn update_enabled(&self) -> bool { + self.update_interval() != 0 + } + + #[must_use] + pub fn update_interval(&self) -> i32 { + self.runtime.update_interval.load(Ordering::Acquire) + } + + pub fn set_update_interval(&mut self, value: i32) { + self.runtime.set_update_interval(value); + } +} + +macro_rules! control_accessors { + ($(($getter:ident, $setter:ident, $flag:expr)),+ $(,)?) => { + impl AgentManagerAgentMovement { + $( + #[must_use] + pub fn $getter(&self) -> bool { + self.runtime.flag($flag) + } + + pub fn $setter(&mut self, value: bool) { + self.runtime.set_flag($flag, value); + } + )+ + } + }; +} + +control_accessors!( + (at_pos, set_at_pos, 1 << 0), + (at_neg, set_at_neg, 1 << 1), + (left_pos, set_left_pos, 1 << 2), + (left_neg, set_left_neg, 1 << 3), + (up_pos, set_up_pos, 1 << 4), + (up_neg, set_up_neg, 1 << 5), + (pitch_pos, set_pitch_pos, 1 << 6), + (pitch_neg, set_pitch_neg, 1 << 7), + (yaw_pos, set_yaw_pos, 1 << 8), + (yaw_neg, set_yaw_neg, 1 << 9), + (fast_at, set_fast_at, 1 << 10), + (fast_left, set_fast_left, 1 << 11), + (fast_up, set_fast_up, 1 << 12), + (fly, set_fly, 1 << 13), + (stop, set_stop, 1 << 14), + (finish_anim, set_finish_anim, 1 << 15), + (stand_up, set_stand_up, 1 << 16), + (sit_on_ground, set_sit_on_ground, 1 << 17), + (mouselook, set_mouselook, 1 << 18), + (nudge_at_pos, set_nudge_at_pos, 1 << 19), + (nudge_at_neg, set_nudge_at_neg, 1 << 20), + (nudge_left_pos, set_nudge_left_pos, 1 << 21), + (nudge_left_neg, set_nudge_left_neg, 1 << 22), + (nudge_up_pos, set_nudge_up_pos, 1 << 23), + (nudge_up_neg, set_nudge_up_neg, 1 << 24), + (turn_left, set_turn_left, 1 << 25), + (turn_right, set_turn_right, 1 << 26), + (away, set_away, 1 << 27), + (l_button_down, set_l_button_down, 1 << 28), + (l_button_up, set_l_button_up, 1 << 29), + (ml_button_down, set_ml_button_down, 1 << 30), + (ml_button_up, set_ml_button_up, 1 << 31), +); + +impl AgentMovementRuntime { + pub(crate) fn subscribe_teleport_progress( + &self, + handler: EventHandler, + ) -> Subscription { + self.teleport_progress.subscribe(handler) + } + + pub(crate) fn subscribe_camera_constraint( + &self, + handler: EventHandler, + ) -> Subscription { + self.camera_constraint.subscribe(handler) + } + + pub(crate) fn subscribe_avatar_sit_response( + &self, + handler: EventHandler, + ) -> Subscription { + self.avatar_sit_response.subscribe(handler) + } + + pub(crate) fn subscribe_region_crossed( + &self, + handler: EventHandler, + ) -> Subscription { + self.region_crossed.subscribe(handler) + } + + pub(crate) fn subscribe_region_crossing_predicted( + &self, + handler: EventHandler, + ) -> Subscription { + self.region_crossing_predicted.subscribe(handler) + } + + pub(crate) fn crossing_state(&self) -> AgentManagerCrossingState { + mutex(&self.crossing.current) + .as_ref() + .map_or(AgentManagerCrossingState::Idle, |crossing| crossing.state) + } + + pub(crate) fn crossing_failure_reason(&self) -> AgentManagerCrossingFailureReason { + mutex(&self.crossing.current) + .as_ref() + .map_or(AgentManagerCrossingFailureReason::Unknown, |crossing| { + crossing.failure_reason + }) + } + + pub(crate) fn crossing_details(&self) -> String { + let current = mutex(&self.crossing.current); + let Some(crossing) = current.as_ref() else { + return "No active crossing".into(); + }; + format!( + "State: {:?}, Duration: {:.2}s, Retries: {}/3, Old Sim: {}, New Sim: {}, Target: {}, Failure: {:?} - {}", + crossing.state, + crossing.started.elapsed().as_secs_f64(), + crossing.retry_count, + crossing + .old_simulator + .as_ref() + .map_or("null", |simulator| simulator.name.as_str()), + crossing + .new_simulator + .as_ref() + .map_or("null", |simulator| simulator.name.as_str()), + crossing.endpoint, + crossing.failure_reason, + crossing.failure_message, + ) + } + + pub(crate) fn is_crossing(&self) -> bool { + !matches!( + self.crossing_state(), + AgentManagerCrossingState::Idle + | AgentManagerCrossingState::Completed + | AgentManagerCrossingState::Failed + ) + } + + pub(crate) fn cancel_crossing(&self) { + let mut current = mutex(&self.crossing.current); + let Some(crossing) = current.as_mut() else { + return; + }; + if matches!( + crossing.state, + AgentManagerCrossingState::Idle + | AgentManagerCrossingState::Completed + | AgentManagerCrossingState::Failed + ) { + return; + } + crossing.failure_message = "Crossing was manually cancelled".into(); + crossing.cancel_requested = true; + drop(current); + self.crossing.wake.notify_all(); + } + + pub(crate) fn acceleration(&self) -> Vector3 { + read(&self.kinematics).acceleration + } + + pub(crate) fn angular_velocity(&self) -> Vector3 { + read(&self.kinematics).angular_velocity + } + + pub(crate) fn global_position(&self) -> Vector3d { + let Some(simulator) = self.network.native_current_sim() else { + return Vector3d::zero(); + }; + let position = self.sim_position(); + let global_x = u32::try_from(simulator.handle >> 32).unwrap_or_default(); + let global_y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default(); + Vector3d { + x: f64::from(global_x) + f64::from(position.x), + y: f64::from(global_y) + f64::from(position.y), + z: f64::from(position.z), + } + } + + pub(crate) fn home_position(&self) -> Vector3 { + read(&self.kinematics).home_position + } + + pub(crate) fn last_position_update(&self) -> SystemTime { + read(&self.kinematics).last_position_update + } + + pub(crate) fn set_last_position_update(&self, value: SystemTime) { + write(&self.kinematics).last_position_update = value; + } + + pub(crate) fn relative_position(&self) -> Vector3 { + read(&self.kinematics).relative_position + } + + pub(crate) fn set_relative_position(&self, value: Vector3) { + write(&self.kinematics).relative_position = value; + } + + pub(crate) fn relative_position_estimate(&self) -> Vector3 { + let state = *read(&self.kinematics); + let elapsed = SystemTime::now() + .duration_since(state.last_position_update) + .unwrap_or_default() + .as_secs_f32(); + add(state.relative_position, scale(state.velocity, elapsed)) + } + + pub(crate) fn relative_rotation(&self) -> Quaternion { + read(&self.kinematics).relative_rotation + } + + pub(crate) fn set_relative_rotation(&self, value: Quaternion) { + write(&self.kinematics).relative_rotation = value; + } + + pub(crate) fn sitting_on(&self) -> u32 { + read(&self.kinematics).sitting_on + } + + pub(crate) fn teleport_message(&self) -> String { + mutex(&self.teleport).message.clone() + } + + pub(crate) fn set_teleport_message(&self, value: String) { + mutex(&self.teleport).message = value; + } + + pub(crate) fn velocity(&self) -> Vector3 { + read(&self.kinematics).velocity + } + + pub(crate) fn request_sit(&self, target_id: UUID, offset: Vector3) -> Result<(), Error> { + let (agent_id, session_id) = self.ids(); + let mut packet = AgentRequestSitPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.target_object.target_id = target_id; + packet.target_object.offset = offset; + self.send_current( + PacketType::AgentRequestSit, + packet.to_bytes_with_method()?, + None, + ) + } + + pub(crate) fn sit(&self) -> Result<(), Error> { + let (agent_id, session_id) = self.ids(); + let mut packet = AgentSitPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + self.send_current(PacketType::AgentSit, packet.to_bytes_with_method()?, None) + } + + pub(crate) fn autopilot(&self, x: String, y: String, z: String) -> Result<(), Error> { + let (agent_id, session_id) = self.ids(); + let mut packet = GenericMessagePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.agent_data.transaction_id = UUID::zero(); + packet.method_data.invoice = UUID::zero(); + packet.method_data.method = b"autopilot".to_vec(); + packet.param_list = [x, y, z] + .into_iter() + .map(|parameter| GenericMessagePacketParamListBlock { + parameter: parameter.into_bytes(), + }) + .collect(); + self.send_current( + PacketType::GenericMessage, + packet.to_bytes_with_method()?, + None, + ) + } + + pub(crate) fn send_lure(&self, target_id: UUID, message: String) -> Result<(), Error> { + let (agent_id, session_id) = self.ids(); + let mut packet = StartLurePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent_id; + packet.agent_data.session_id = session_id; + packet.info.lure_type = 0; + packet.info.message = message.into_bytes(); + packet.target_data = vec![StartLurePacketTargetDataBlock { target_id }]; + self.send_current(PacketType::StartLure, packet.to_bytes_with_method()?, None) + } + + pub(crate) fn accept_lure(&self, session_id: UUID) -> Result<(), Error> { + let (agent_id, own_session_id) = self.ids(); + let mut packet = TeleportLureRequestPacket::new_with_constructor()?; + packet.info.agent_id = agent_id; + packet.info.session_id = own_session_id; + packet.info.lure_id = session_id; + packet.info.teleport_flags = TeleportFlags::VIA_LURE.0; + self.send_current( + PacketType::TeleportLureRequest, + packet.to_bytes_with_method()?, + None, + ) + } +} + +impl AgentManager { + pub(crate) fn native_movement_runtime(&self) -> &Arc { + &self.movement.runtime + } + + pub(crate) fn native_subscribe_teleport_progress( + &self, + handler: EventHandler, + ) -> Subscription { + self.movement.runtime.subscribe_teleport_progress(handler) + } + + pub(crate) fn native_subscribe_camera_constraint( + &self, + handler: EventHandler, + ) -> Subscription { + self.movement.runtime.subscribe_camera_constraint(handler) + } + + pub(crate) fn native_subscribe_avatar_sit_response( + &self, + handler: EventHandler, + ) -> Subscription { + self.movement.runtime.subscribe_avatar_sit_response(handler) + } + + pub(crate) fn native_subscribe_region_crossed( + &self, + handler: EventHandler, + ) -> Subscription { + self.movement.runtime.subscribe_region_crossed(handler) + } + + pub(crate) fn native_subscribe_region_crossing_predicted( + &self, + handler: EventHandler, + ) -> Subscription { + self.movement + .runtime + .subscribe_region_crossing_predicted(handler) + } + + pub(crate) fn native_track_object_in_simulator(&self, object_id: UUID, simulator: Simulator) { + self.movement.runtime.track_object(object_id, simulator); + } + + pub(crate) fn native_untrack_object_in_simulator( + &self, + object_id: UUID, + simulator: &Simulator, + ) { + self.movement.runtime.untrack_object(object_id, simulator); + } + + pub(crate) fn native_get_simulators_for_object(&self, object_id: UUID) -> Vec { + self.movement.runtime.object_simulators(object_id) + } + + #[allow(clippy::unused_self)] // Public C# method is instance-shaped but uses only its inputs. + pub(crate) fn native_is_near_border( + &self, + position: Vector3, + region_size_x: u32, + region_size_y: u32, + threshold: f32, + ) -> bool { + position.x < threshold + || position.x > region_size_x as f32 - threshold + || position.y < threshold + || position.y > region_size_y as f32 - threshold + } + + pub(crate) fn native_cancel_crossing(&self) { + self.movement.runtime.cancel_crossing(); + } + + pub(crate) fn native_crossing_state(&self) -> AgentManagerCrossingState { + self.movement.runtime.crossing_state() + } + + pub(crate) fn native_crossing_failure_reason(&self) -> AgentManagerCrossingFailureReason { + self.movement.runtime.crossing_failure_reason() + } + + pub(crate) fn native_crossing_details(&self) -> String { + self.movement.runtime.crossing_details() + } + + pub(crate) fn native_is_crossing(&self) -> bool { + self.movement.runtime.is_crossing() + } + + pub(crate) fn native_complete_agent_movement(&self, simulator: Simulator) -> Result<(), Error> { + self.movement.runtime.complete_agent_movement(simulator) + } + + pub(crate) fn native_request_sit(&self, target_id: UUID, offset: Vector3) -> Result<(), Error> { + self.movement.runtime.request_sit(target_id, offset) + } + + pub(crate) fn native_sit(&self) -> Result<(), Error> { + self.movement.runtime.sit() + } + + pub(crate) fn native_stand(&self) -> Result { + if !self.client.settings_ref().agent.send_updates { + return Ok(false); + } + self.movement.runtime.set_flag(1 << 17, false); + self.movement.runtime.set_flag(1 << 16, true); + self.movement.runtime.send_last_update(false, None)?; + self.movement.runtime.set_flag(1 << 16, false); + self.movement.runtime.send_last_update(false, None)?; + Ok(true) + } + + pub(crate) fn native_sit_on_ground(&self) -> Result<(), Error> { + self.movement.runtime.set_flag(1 << 17, true); + self.movement.runtime.send_last_update(true, None) + } + + pub(crate) fn native_fly(&self, start: bool) -> Result<(), Error> { + self.movement.runtime.set_flag(1 << 13, start); + self.movement.runtime.send_last_update(true, None) + } + + pub(crate) fn native_crouch(&self, crouching: bool) -> Result<(), Error> { + self.movement.runtime.set_flag(1 << 5, crouching); + self.movement.runtime.send_last_update(true, None) + } + + pub(crate) fn native_jump(&self, jumping: bool) -> Result<(), Error> { + self.movement.runtime.set_flag(1 << 4, jumping); + self.movement.runtime.set_flag(1 << 12, jumping); + self.movement.runtime.send_last_update(true, None) + } + + pub(crate) fn native_autopilot_f64(&self, x: f64, y: f64, z: f64) -> Result<(), Error> { + self.movement + .runtime + .autopilot(x.to_string(), y.to_string(), z.to_string()) + } + + pub(crate) fn native_autopilot_u64(&self, x: u64, y: u64, z: f32) -> Result<(), Error> { + self.movement + .runtime + .autopilot(x.to_string(), y.to_string(), z.to_string()) + } + + pub(crate) fn native_autopilot_local(&self, x: i32, y: i32, z: f32) -> Result<(), Error> { + let Some(simulator) = self.client.network().native_current_sim() else { + return Ok(()); + }; + let global_x = i64::from(u32::try_from(simulator.handle >> 32).unwrap_or_default()) + .checked_add(i64::from(x)) + .ok_or(Error::Argument)?; + let global_y = + i64::from(u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default()) + .checked_add(i64::from(y)) + .ok_or(Error::Argument)?; + self.movement.runtime.autopilot( + u64::try_from(global_x) + .map_err(|_| Error::Argument)? + .to_string(), + u64::try_from(global_y) + .map_err(|_| Error::Argument)? + .to_string(), + z.to_string(), + ) + } + + pub(crate) fn native_autopilot_cancel(&self) -> Result { + if !self.client.settings_ref().agent.send_updates { + return Ok(false); + } + self.movement.runtime.set_flag(1, true); + self.movement.runtime.send_last_update(false, None)?; + self.movement.runtime.set_flag(1, false); + self.movement.runtime.send_last_update(false, None)?; + Ok(true) + } + + pub(crate) fn native_request_teleport_landmark(&self, landmark: UUID) -> Result<(), Error> { + self.movement.runtime.request_teleport_landmark(landmark) + } + + pub(crate) fn native_request_teleport_location( + &self, + region_handle: u64, + position: Vector3, + look_at: Vector3, + ignore_caps_status: Option, + ) -> Result<(), Error> { + self.movement.runtime.request_teleport_location( + region_handle, + position, + look_at, + ignore_caps_status.unwrap_or(false), + ) + } + + pub(crate) async fn native_teleport_landmark( + &self, + landmark: UUID, + cancellation_token: Option, + ) -> Result { + if mutex(&self.movement.runtime.teleport).status == TeleportStatus::Progress { + return Ok(false); + } + let (generation, receiver) = self.movement.runtime.begin_teleport(); + self.movement.runtime.request_teleport_landmark(landmark)?; + self.movement + .runtime + .wait_for_teleport(generation, receiver, cancellation_token) + .await + } + + pub(crate) async fn native_teleport_location( + &self, + region_handle: u64, + position: Vector3, + look_at: Vector3, + cancellation_token: Option, + ) -> Result { + self.movement + .runtime + .wait_for_event_queue(cancellation_token.clone()) + .await?; + let (generation, receiver) = self.movement.runtime.begin_teleport(); + self.movement + .runtime + .request_teleport_location(region_handle, position, look_at, true)?; + self.movement + .runtime + .wait_for_teleport(generation, receiver, cancellation_token) + .await + } + + pub(crate) async fn native_teleport_name( + &self, + sim_name: String, + position: Vector3, + look_at: Vector3, + cancellation_token: Option, + ) -> Result { + if sim_name.is_empty() { + self.movement.runtime.teleport_event( + "Invalid simulator name".into(), + TeleportStatus::Failed, + TeleportFlags::DEFAULT, + ); + return Ok(false); + } + let Some(current) = self.client.network().native_current_sim() else { + self.movement.runtime.teleport_event( + format!("Not in a current simulator, cannot teleport to {sim_name}"), + TeleportStatus::Failed, + TeleportFlags::DEFAULT, + ); + return Ok(false); + }; + let handle = if current.name.eq_ignore_ascii_case(&sim_name) { + Some(current.handle) + } else { + self.client + .network() + .simulators + .snapshot() + .into_iter() + .find(|simulator| simulator.name.eq_ignore_ascii_case(&sim_name)) + .map(|simulator| simulator.handle) + }; + let Some(handle) = handle else { + self.movement.runtime.teleport_event( + format!("Unable to resolve simulator named: {sim_name}"), + TeleportStatus::Failed, + TeleportFlags::DEFAULT, + ); + return Ok(false); + }; + self.native_teleport_location(handle, position, look_at, cancellation_token) + .await + } + + pub(crate) fn native_send_teleport_lure( + &self, + target_id: UUID, + message: Option, + ) -> Result<(), Error> { + let message = message.unwrap_or_else(|| { + let region = self + .client + .network() + .native_current_sim() + .map_or_else(|| "unknown".into(), |simulator| simulator.name.clone()); + format!("Join me in {region}!") + }); + self.movement.runtime.send_lure(target_id, message) + } + + pub(crate) fn native_teleport_lure_respond( + &self, + requester_id: UUID, + session_id: UUID, + accept: bool, + ) -> Result<(), Error> { + if accept { + self.movement.runtime.accept_lure(session_id) + } else { + self.native_instant_message_full( + self.native_name(), + requester_id, + String::new(), + session_id, + InstantMessageDialog::DenyTeleport, + InstantMessageOnline::Offline, + self.movement.runtime.sim_position(), + UUID::zero(), + Vec::new(), + ) + } + } + + pub(crate) fn native_send_teleport_lure_request( + &self, + target_id: UUID, + session_id: UUID, + message: String, + ) -> Result<(), Error> { + if target_id == self.native_agent_id() { + return Ok(()); + } + self.native_instant_message_full( + self.native_name(), + target_id, + message, + session_id, + InstantMessageDialog::RequestLure, + InstantMessageOnline::Offline, + self.movement.runtime.sim_position(), + UUID::zero(), + Vec::new(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::{ + Packet, PacketAckPacket, PacketAckPacketPacketsBlock, RegionHandshakePacket, + }; + use libremetaverse_types::compat::CancellationTokenSource; + use std::sync::mpsc::{self, Receiver, Sender}; + + fn approx(actual: Vector3, expected: Vector3) { + assert!((actual.x - expected.x).abs() < 0.000_1, "x: {actual:?}"); + assert!((actual.y - expected.y).abs() < 0.000_1, "y: {actual:?}"); + assert!((actual.z - expected.z).abs() < 0.000_1, "z: {actual:?}"); + } + + 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 + } + + 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 + } + + struct FakeGrid { + endpoint: SocketAddr, + packets: Receiver<(PacketType, Vec)>, + shutdown: Sender<()>, + thread: Option>, + } + + impl FakeGrid { + fn start() -> Self { + use std::io::ErrorKind; + use std::net::UdpSocket; + use std::sync::mpsc::TryRecvError; + + let socket = + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(); + socket + .set_read_timeout(Some(Duration::from_millis(100))) + .unwrap(); + let endpoint = socket.local_addr().unwrap(); + let (packet_sender, packets) = mpsc::channel(); + let (shutdown, shutdown_receiver) = mpsc::channel(); + let thread = thread::spawn(move || { + let mut buffer = [0_u8; 8192]; + let (length, client_endpoint) = socket.recv_from(&mut buffer).unwrap(); + 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"Movement Test Region\0".to_vec(); + let mut bytes = handshake.to_bytes_with_method().unwrap(); + bytes[0] &= !(crate::Helpers::MSG_RELIABLE | crate::Helpers::MSG_ZEROCODED); + socket.send_to(&bytes, client_endpoint).unwrap(); + + loop { + match shutdown_receiver.try_recv() { + Ok(()) | Err(TryRecvError::Disconnected) => break, + Err(TryRecvError::Empty) => {} + } + let (length, _) = match socket.recv_from(&mut buffer) { + Ok(value) => value, + Err(error) + if matches!( + error.kind(), + ErrorKind::WouldBlock | ErrorKind::TimedOut + ) => + { + continue; + } + Err(_) => break, + }; + 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::PacketAck | PacketType::RegionHandshakeReply + ) { + packet_sender + .send((kind, buffer[..length].to_vec())) + .unwrap(); + } + } + }); + Self { + endpoint, + packets, + shutdown, + thread: Some(thread), + } + } + + fn receive(&self, expected: PacketType) -> Vec { + loop { + let (kind, bytes) = self + .packets + .recv_timeout(Duration::from_secs(3)) + .expect("fake grid packet"); + if kind == expected { + return bytes; + } + } + } + } + + impl Drop for FakeGrid { + fn drop(&mut self) { + let _ = self.shutdown.send(()); + if let Some(handle) = self.thread.take() { + handle.join().unwrap(); + } + } + } + + #[test] + fn camera_math_preserves_the_reference_frame_axis_mapping() { + let camera = AgentManagerAgentMovementAgentCamera::new().unwrap(); + approx( + camera.position(), + Vector3 { + x: 128.0, + y: 128.0, + z: 20.0, + }, + ); + camera + .look_direction_with_vector3(Vector3::unit_x()) + .unwrap(); + approx(camera.left_axis(), Vector3::unit_x()); + approx(camera.at_axis(), Vector3::unit_y()); + approx(camera.up_axis(), Vector3::unit_z()); + + camera + .look_at_with_vector3_vector3( + Vector3::zero(), + Vector3 { + x: 0.0, + y: 10.0, + z: 0.0, + }, + ) + .unwrap(); + approx(camera.left_axis(), Vector3::unit_y()); + approx( + camera.at_axis(), + Vector3 { + x: -1.0, + y: 0.0, + z: 0.0, + }, + ); + assert!(camera.look_direction_with_double(f64::NAN).is_err()); + } + + #[test] + fn control_bits_and_reset_rules_match_agent_movement() { + let client = Arc::new(GridClient::new().unwrap()); + let manager = AgentManager::native_new(Some(client)).unwrap(); + let mut movement = manager.movement; + movement.set_agent_controls(u32::MAX); + movement.reset_control_flags().unwrap(); + assert_eq!( + movement.agent_controls(), + (1 << 27) | (1 << 13) | (1 << 18) | (1 << 5) + ); + movement.set_at_pos(true); + movement.set_turn_right(true); + assert!(movement.at_pos()); + assert!(movement.turn_right()); + movement.set_update_interval(-5); + assert_eq!(movement.update_interval(), 0); + assert!(!movement.update_enabled()); + } + + #[test] + fn fake_grid_decodes_locomotion_camera_and_autopilot_packets() { + let grid = FakeGrid::start(); + let mut client = GridClient::new().unwrap(); + client.settings().timing().login_timeout = 2_000; + let client = Arc::new(client); + let mut manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + manager.native_set_agent_id(UUID::random().unwrap()); + manager.native_set_session_id(UUID::random().unwrap()); + let mut network = client.network(); + network.set_circuit_code(0x1234_5678); + let simulator = network + .native_connect(grid.endpoint, 0x1000, true, None, 256, 256) + .unwrap() + .unwrap(); + let _ = grid.receive(PacketType::CompleteAgentMovement); + + let mut complete = AgentMovementCompletePacket::new_with_constructor().unwrap(); + complete.data.position = Vector3 { + x: 10.0, + y: 20.0, + z: 30.0, + }; + complete.data.look_at = Vector3::unit_x(); + manager + .native_movement_runtime() + .handle_raw_packet( + PacketType::AgentMovementComplete, + &transport_wire(complete.to_bytes_with_method().unwrap()), + simulator.clone(), + ) + .unwrap(); + assert_eq!(manager.sim_position(), complete.data.position); + + manager.native_fly(true).unwrap(); + let update = + decode_packet::(&grid.receive(PacketType::AgentUpdate)).unwrap(); + assert_eq!(update.agent_data.control_flags, 1 << 13); + assert_eq!(update.agent_data.agent_id, manager.native_agent_id()); + assert_eq!(update.agent_data.session_id, manager.native_session_id()); + + manager.movement.set_always_run(true); + let run = + decode_packet::(&grid.receive(PacketType::SetAlwaysRun)).unwrap(); + assert!(run.agent_data.always_run); + + manager.movement.set_fov_vertical_angle(1.25).unwrap(); + let fov = decode_packet::(&grid.receive(PacketType::AgentFOV)).unwrap(); + assert_eq!(fov.fov_block.vertical_angle, 1.25); + + manager.native_autopilot_u64(1000, 2000, 30.5).unwrap(); + let autopilot = + decode_packet::(&grid.receive(PacketType::GenericMessage)) + .unwrap(); + assert_eq!(wire_string(autopilot.method_data.method), "autopilot"); + assert_eq!(autopilot.param_list.len(), 3); + assert_eq!( + wire_string(autopilot.param_list[0].parameter.clone()), + "1000" + ); + assert_eq!( + wire_string(autopilot.param_list[1].parameter.clone()), + "2000" + ); + assert_eq!( + wire_string(autopilot.param_list[2].parameter.clone()), + "30.5" + ); + + manager.movement.set_auto_reset_controls(true); + manager.movement.set_at_pos(true); + manager + .movement + .send_update_with_boolean(Some(false)) + .unwrap(); + let _ = grid.receive(PacketType::AgentUpdate); + assert_eq!(manager.movement.agent_controls(), 1 << 13); + + manager.dispose().unwrap(); + simulator.native_disconnect(false).unwrap(); + } + + #[tokio::test(flavor = "current_thread", start_paused = true)] + async fn teleport_completion_timeout_and_cancellation_are_deterministic() { + let mut client = GridClient::new().unwrap(); + client.settings().timing().teleport_timeout = 5_000; + let client = Arc::new(client); + let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + let runtime = Arc::clone(manager.native_movement_runtime()); + let simulator = Simulator::native_new( + (*client).clone(), + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13001), + 0x1000, + Some(256), + Some(256), + ) + .unwrap(); + runtime + .network + .native_set_current_sim(Some(simulator.clone())); + let queue_source = CancellationTokenSource::new(); + let queue_token = queue_source.token(); + let queue_runtime = Arc::clone(&runtime); + let queue_wait = + tokio::spawn( + async move { queue_runtime.wait_for_event_queue(Some(queue_token)).await }, + ); + tokio::task::yield_now().await; + queue_source.cancel(); + assert!(matches!(queue_wait.await.unwrap(), Err(Error::Cancelled))); + + let events = Arc::new(Mutex::new(Vec::new())); + let recorded = Arc::clone(&events); + let _subscription = runtime.subscribe_teleport_progress(Arc::new(move |event| { + mutex(&recorded).push((event.status(), event.message())); + })); + + let (generation, receiver) = runtime.begin_teleport(); + let mut local = TeleportLocalPacket::new_with_constructor().unwrap(); + local.info.position = Vector3 { + x: 40.0, + y: 50.0, + z: 60.0, + }; + local.info.look_at = Vector3::unit_y(); + runtime + .handle_raw_packet( + PacketType::TeleportLocal, + &transport_wire(local.to_bytes_with_method().unwrap()), + simulator.clone(), + ) + .unwrap(); + assert!( + runtime + .wait_for_teleport(generation, receiver, None) + .await + .unwrap() + ); + assert_eq!(runtime.sim_position(), local.info.position); + assert_eq!( + mutex(&events).as_slice(), + &[(TeleportStatus::Finished, "Teleport finished".into())] + ); + + let (generation, receiver) = runtime.begin_teleport(); + let timeout_runtime = Arc::clone(&runtime); + let timeout = tokio::spawn(async move { + timeout_runtime + .wait_for_teleport(generation, receiver, None) + .await + }); + tokio::task::yield_now().await; + tokio::time::advance(Duration::from_secs(5)).await; + assert!(!timeout.await.unwrap().unwrap()); + assert_eq!(mutex(&runtime.teleport).status, TeleportStatus::Failed); + + let (generation, receiver) = runtime.begin_teleport(); + let source = CancellationTokenSource::new(); + let token = source.token(); + let cancelled_runtime = Arc::clone(&runtime); + let cancelled = tokio::spawn(async move { + cancelled_runtime + .wait_for_teleport(generation, receiver, Some(token)) + .await + }); + tokio::task::yield_now().await; + source.cancel(); + assert!(matches!(cancelled.await.unwrap(), Err(Error::Cancelled))); + assert!(mutex(&runtime.teleport).waiter.is_none()); + + manager.dispose().unwrap(); + } + + #[test] + fn crossing_diagnostics_and_manual_cancellation_are_consistent() { + let client = Arc::new(GridClient::new().unwrap()); + let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + let runtime = manager.native_movement_runtime(); + let simulator = Simulator::native_new( + (*client).clone(), + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13002), + 0x1000, + Some(256), + Some(256), + ) + .unwrap(); + *mutex(&runtime.crossing.current) = Some(CrossingInfo { + state: AgentManagerCrossingState::WaitingForComplete, + started: Instant::now(), + old_simulator: Some(simulator), + new_simulator: None, + region_handle: 0x2000, + endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13003), + seed: Uri("https://caps.test/seed".into()), + position: Vector3::zero(), + look_at: Vector3::unit_x(), + size_x: 256, + size_y: 256, + retry_count: 2, + failure_reason: AgentManagerCrossingFailureReason::ConnectionFailed, + failure_message: "connection failed".into(), + generation: 1, + cancel_requested: false, + restored_old_simulator: false, + }); + assert!(manager.is_crossing().unwrap()); + assert_eq!( + manager.get_crossing_state().unwrap(), + AgentManagerCrossingState::WaitingForComplete + ); + assert_eq!( + manager.get_crossing_failure_reason().unwrap(), + AgentManagerCrossingFailureReason::ConnectionFailed + ); + let details = manager.get_crossing_details().unwrap(); + assert!(details.contains("State: WaitingForComplete")); + assert!(details.contains("Retries: 2/3")); + manager.cancel_crossing().unwrap(); + assert!( + mutex(&runtime.crossing.current) + .as_ref() + .unwrap() + .cancel_requested + ); + assert!( + manager + .get_crossing_details() + .unwrap() + .contains("Crossing was manually cancelled") + ); + manager.dispose().unwrap(); + } + + #[test] + fn fake_grids_complete_a_region_crossing_and_raise_the_final_event() { + let old_grid = FakeGrid::start(); + let new_grid = FakeGrid::start(); + let mut client = GridClient::new().unwrap(); + client.settings().timing().login_timeout = 2_000; + let client = Arc::new(client); + let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + let mut network = client.network(); + network.set_circuit_code(0x1234_5678); + let old_simulator = network + .native_connect(old_grid.endpoint, 0x1000, true, None, 256, 256) + .unwrap() + .unwrap(); + let _ = old_grid.receive(PacketType::CompleteAgentMovement); + old_simulator.native_set_agent_movement_complete(true); + + let (event_sender, event_receiver) = mpsc::channel(); + let _subscription = manager.native_subscribe_region_crossed(Arc::new(move |event| { + event_sender.send(event).unwrap(); + })); + let runtime = manager.native_movement_runtime(); + assert!(runtime.begin_crossing( + old_simulator.clone(), + new_grid.endpoint, + 0x2000, + Uri("http://127.0.0.1:1/seed".into()), + Vector3 { + x: 12.0, + y: 24.0, + z: 36.0, + }, + Vector3::unit_y(), + 256, + 256, + )); + let _ = new_grid.receive(PacketType::CompleteAgentMovement); + + let current = mutex(&runtime.crossing.current); + let (current, timeout) = runtime + .crossing + .wake + .wait_timeout_while(current, Duration::from_secs(3), |current| { + current.as_ref().is_none_or(|crossing| { + crossing.state != AgentManagerCrossingState::WaitingForComplete + }) + }) + .unwrap_or_else(std::sync::PoisonError::into_inner); + assert!(!timeout.timed_out(), "crossing did not reach movement wait"); + let new_simulator = current + .as_ref() + .and_then(|crossing| crossing.new_simulator.clone()) + .expect("destination simulator"); + drop(current); + + let mut complete = AgentMovementCompletePacket::new_with_constructor().unwrap(); + complete.data.position = Vector3 { + x: 12.0, + y: 24.0, + z: 36.0, + }; + complete.data.look_at = Vector3::unit_y(); + runtime + .handle_raw_packet( + PacketType::AgentMovementComplete, + &transport_wire(complete.to_bytes_with_method().unwrap()), + new_simulator.clone(), + ) + .unwrap(); + let event = event_receiver + .recv_timeout(Duration::from_secs(3)) + .expect("RegionCrossed event"); + assert_eq!(event.old_simulator(), Some(old_simulator.clone())); + assert_eq!(event.new_simulator(), Some(new_simulator.clone())); + assert_eq!( + manager.get_crossing_state().unwrap(), + AgentManagerCrossingState::Idle + ); + assert_eq!(network.native_current_sim(), Some(new_simulator.clone())); + assert!(!old_simulator.native_agent_movement_complete()); + + manager.dispose().unwrap(); + old_simulator.native_disconnect(false).unwrap(); + new_simulator.native_disconnect(false).unwrap(); + } + + #[test] + fn multi_simulator_prediction_and_object_visibility_match_reference_rules() { + let mut client = GridClient::new().unwrap(); + client.settings().agent_settings_mut().multiple_sims = true; + let client = Arc::new(client); + let manager = AgentManager::native_new(Some(Arc::clone(&client))).unwrap(); + let simulator = Simulator::native_new( + (*client).clone(), + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13010), + (u64::from(1024_u32) << 32) | u64::from(2048_u32), + Some(256), + Some(256), + ) + .unwrap(); + let other = Simulator::native_new( + (*client).clone(), + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13011), + (u64::from(1280_u32) << 32) | u64::from(2048_u32), + Some(256), + Some(256), + ) + .unwrap(); + simulator.native_set_agent_movement_complete(true); + let runtime = manager.native_movement_runtime(); + { + let mut state = write(&runtime.kinematics); + state.relative_position = Vector3 { + x: 255.0, + y: 128.0, + z: 20.0, + }; + state.velocity = Vector3::unit_x(); + } + let (prediction_sender, prediction_receiver) = mpsc::channel(); + let _prediction = + manager.native_subscribe_region_crossing_predicted(Arc::new(move |event| { + prediction_sender.send(event).unwrap() + })); + runtime.update_multi_simulator_state(&simulator); + let prediction = prediction_receiver.try_recv().unwrap(); + assert_eq!(prediction.current_simulator(), simulator); + assert_eq!(prediction.direction(), BorderCrossingDirection::East); + assert_eq!(prediction.time_until_crossing(), 1.0); + let state = read(&runtime.simulator_states)[&simulator.native_ip_end_point()]; + assert_eq!(state.position.x, 255.0); + assert!(state.is_present); + + let object_id = UUID::random().unwrap(); + manager + .track_object_in_simulator(object_id, simulator.clone()) + .unwrap(); + manager + .track_object_in_simulator(object_id, other.clone()) + .unwrap(); + manager + .track_object_in_simulator(object_id, simulator.clone()) + .unwrap(); + assert_eq!( + manager.get_simulators_for_object(object_id).unwrap().len(), + 2 + ); + manager + .untrack_object_in_simulator(object_id, simulator) + .unwrap(); + assert_eq!( + manager.get_simulators_for_object(object_id).unwrap(), + [other] + ); + assert!( + manager + .is_near_border( + Vector3 { + x: 31.0, + y: 128.0, + z: 0.0, + }, + 256, + 256, + None, + ) + .unwrap() + ); + assert!( + !manager + .is_near_border( + Vector3 { + x: 128.0, + y: 128.0, + z: 0.0, + }, + 256, + 256, + None, + ) + .unwrap() + ); + manager.dispose().unwrap(); + } +} diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index a149ce0..ce75472 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -259,7 +259,7 @@ impl AgentManager { libremetaverse::AvatarSitResponseEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.AvatarSitResponse") + self.native_subscribe_avatar_sit_response(handler) } /// C# member: `E:LibreMetaverse.AgentManager.CameraConstraint`. pub fn subscribe_camera_constraint( @@ -268,7 +268,7 @@ impl AgentManager { libremetaverse::CameraConstraintEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.CameraConstraint") + self.native_subscribe_camera_constraint(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ChatFromSimulator`. pub fn subscribe_chat_from_simulator( @@ -389,7 +389,7 @@ impl AgentManager { &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.RegionCrossed") + self.native_subscribe_region_crossed(handler) } /// C# member: `E:LibreMetaverse.AgentManager.RegionCrossingPredicted`. pub fn subscribe_region_crossing_predicted( @@ -398,9 +398,7 @@ impl AgentManager { libremetaverse::RegionCrossingPredictionEventArgs, >, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.AgentManager.RegionCrossingPredicted" - ) + self.native_subscribe_region_crossing_predicted(handler) } /// C# member: `E:LibreMetaverse.AgentManager.RegionExperiencesUpdated`. pub fn subscribe_region_experiences_updated( @@ -459,7 +457,7 @@ impl AgentManager { &self, handler: libremetaverse_types::compat::EventHandler, ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.AgentManager.TeleportProgress") + self.native_subscribe_teleport_progress(handler) } /// C# member: `E:LibreMetaverse.AgentManager.ViewerBenefitsUpdated`. pub fn subscribe_viewer_benefits_updated( @@ -531,9 +529,7 @@ impl AgentManager { global_y: f64, z: f64, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AutoPilot(System.Double,System.Double,System.Double)", - ) + self.native_autopilot_f64(global_x, global_y, z) } /// C# member: `M:LibreMetaverse.AgentManager.AutoPilot(System.UInt64,System.UInt64,System.Single)`. pub fn auto_pilot_with_u_int64_u_int64_single( @@ -542,19 +538,15 @@ impl AgentManager { global_y: u64, z: f32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AutoPilot(System.UInt64,System.UInt64,System.Single)", - ) + self.native_autopilot_u64(global_x, global_y, z) } /// C# member: `M:LibreMetaverse.AgentManager.AutoPilotCancel`. pub fn auto_pilot_cancel(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.AutoPilotCancel") + self.native_autopilot_cancel() } /// C# member: `M:LibreMetaverse.AgentManager.AutoPilotLocal(System.Int32,System.Int32,System.Single)`. pub fn auto_pilot_local(&self, local_x: i32, local_y: i32, z: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AutoPilotLocal(System.Int32,System.Int32,System.Single)", - ) + self.native_autopilot_local(local_x, local_y, z) } /// C# member: `M:LibreMetaverse.AgentManager.BeamEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)`. pub fn beam_effect( @@ -579,7 +571,7 @@ impl AgentManager { } /// C# member: `M:LibreMetaverse.AgentManager.CancelCrossing`. pub fn cancel_crossing(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.CancelCrossing") + Ok(self.native_cancel_crossing()) } /// C# member: `M:LibreMetaverse.AgentManager.Chat(System.String,System.Int32,LibreMetaverse.ChatType,System.Boolean)`. pub fn chat( @@ -605,15 +597,11 @@ impl AgentManager { &self, simulator: libremetaverse::Simulator, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.CompleteAgentMovement(LibreMetaverse.Simulator)", - ) + self.native_complete_agent_movement(simulator) } /// C# member: `M:LibreMetaverse.AgentManager.Crouch(System.Boolean)`. pub fn crouch(&self, crouching: bool) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.Crouch(System.Boolean)", - ) + self.native_crouch(crouching) } /// C# member: `M:LibreMetaverse.AgentManager.DeGrab(System.UInt32)`. pub fn de_grab_with_u_int32(&self, object_local_id: u32) -> Result<(), crate::Error> { @@ -665,7 +653,7 @@ impl AgentManager { } /// C# member: `M:LibreMetaverse.AgentManager.Fly(System.Boolean)`. pub fn fly(&self, start: bool) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.Fly(System.Boolean)") + self.native_fly(start) } /// C# member: `M:LibreMetaverse.AgentManager.ForgetExperiencePermissionAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn forget_experience_permission( @@ -739,21 +727,19 @@ impl AgentManager { } /// C# member: `M:LibreMetaverse.AgentManager.GetCrossingDetails`. pub fn get_crossing_details(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.GetCrossingDetails") + Ok(self.native_crossing_details()) } /// C# member: `M:LibreMetaverse.AgentManager.GetCrossingFailureReason`. pub fn get_crossing_failure_reason( &self, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetCrossingFailureReason", - ) + Ok(self.native_crossing_failure_reason()) } /// C# member: `M:LibreMetaverse.AgentManager.GetCrossingState`. pub fn get_crossing_state( &self, ) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.GetCrossingState") + Ok(self.native_crossing_state()) } /// C# member: `M:LibreMetaverse.AgentManager.GetExperienceInfoAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)`. pub async fn get_experience_info( @@ -812,9 +798,7 @@ impl AgentManager { &self, object_id: libremetaverse_types::UUID, ) -> Result, crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GetSimulatorsForObject(LibreMetaverse.UUID)", - ) + Ok(self.native_get_simulators_for_object(object_id)) } /// C# member: `M:LibreMetaverse.AgentManager.GetViewerBenefitsAsync(System.Threading.CancellationToken)`. pub async fn get_viewer_benefits( @@ -882,9 +866,8 @@ impl AgentManager { &self, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.GoHomeAsync(System.Threading.CancellationToken)", - ) + self.native_teleport_landmark(libremetaverse_types::UUID::zero(), cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.Grab(System.UInt32)`. pub fn grab_with_u_int32(&self, object_local_id: u32) -> Result<(), crate::Error> { @@ -1020,7 +1003,7 @@ impl AgentManager { } /// C# member: `M:LibreMetaverse.AgentManager.IsCrossing`. pub fn is_crossing(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.IsCrossing") + Ok(self.native_is_crossing()) } /// C# member: `M:LibreMetaverse.AgentManager.IsExperienceAdminAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn is_experience_admin( @@ -1055,13 +1038,16 @@ impl AgentManager { region_size_y: u32, threshold: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.IsNearBorder(LibreMetaverse.Vector3,System.UInt32,System.UInt32,System.Single)", - ) + Ok(self.native_is_near_border( + position, + region_size_x, + region_size_y, + threshold.unwrap_or(32.0), + )) } /// C# member: `M:LibreMetaverse.AgentManager.Jump(System.Boolean)`. pub fn jump(&self, jumping: bool) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.Jump(System.Boolean)") + self.native_jump(jumping) } /// C# member: `M:LibreMetaverse.AgentManager.LookAtEffect(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.Vector3d,LibreMetaverse.LookAtType,LibreMetaverse.UUID)`. pub fn look_at_effect( @@ -1262,18 +1248,14 @@ impl AgentManager { target_id: libremetaverse_types::UUID, offset: libremetaverse_types::Vector3, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestSit(LibreMetaverse.UUID,LibreMetaverse.Vector3)", - ) + self.native_request_sit(target_id, offset) } /// C# member: `M:LibreMetaverse.AgentManager.RequestTeleport(LibreMetaverse.UUID)`. pub fn request_teleport_with_uuid( &self, landmark: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestTeleport(LibreMetaverse.UUID)", - ) + self.native_request_teleport_landmark(landmark) } /// C# member: `M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3)`. pub fn request_teleport_with_u_int64_vector3( @@ -1281,8 +1263,11 @@ impl AgentManager { region_handle: u64, position: libremetaverse_types::Vector3, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3)", + self.native_request_teleport_location( + region_handle, + position, + libremetaverse_types::Vector3::unit_y(), + Some(false), ) } /// C# member: `M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean)`. @@ -1293,9 +1278,7 @@ impl AgentManager { look_at: libremetaverse_types::Vector3, ignore_caps_status: Option, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean)", - ) + self.native_request_teleport_location(region_handle, position, look_at, ignore_caps_status) } /// C# member: `M:LibreMetaverse.AgentManager.RetrieveInstantMessagesAsync(System.Threading.CancellationToken)`. pub async fn retrieve_instant_messages( @@ -1344,9 +1327,7 @@ impl AgentManager { &self, target_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendTeleportLure(LibreMetaverse.UUID)", - ) + self.native_send_teleport_lure(target_id, None) } /// C# member: `M:LibreMetaverse.AgentManager.SendTeleportLure(LibreMetaverse.UUID,System.String)`. pub fn send_teleport_lure_with_uuid_string( @@ -1354,17 +1335,17 @@ impl AgentManager { target_id: libremetaverse_types::UUID, message: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendTeleportLure(LibreMetaverse.UUID,System.String)", - ) + self.native_send_teleport_lure(target_id, Some(message)) } /// C# member: `M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID)`. pub fn send_teleport_lure_request_with_uuid( &self, target_id: libremetaverse_types::UUID, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID)", + self.native_send_teleport_lure_request( + target_id, + target_id, + "Let me join you in your location".into(), ) } /// C# member: `M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)`. @@ -1374,9 +1355,7 @@ impl AgentManager { session_id: libremetaverse_types::UUID, message: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)", - ) + self.native_send_teleport_lure_request(target_id, session_id, message) } /// C# member: `M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID,System.String)`. pub fn send_teleport_lure_request_with_uuid_string( @@ -1384,9 +1363,7 @@ impl AgentManager { target_id: libremetaverse_types::UUID, message: String, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID,System.String)", - ) + self.native_send_teleport_lure_request(target_id, target_id, message) } /// C# member: `M:LibreMetaverse.AgentManager.SendViewerStatsAsync(LibreMetaverse.Messages.Linden.ViewerStatsMessage,System.Threading.CancellationToken)`. pub async fn send_viewer_stats( @@ -1456,11 +1433,11 @@ impl AgentManager { } /// C# member: `M:LibreMetaverse.AgentManager.Sit`. pub fn sit(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.Sit") + self.native_sit() } /// C# member: `M:LibreMetaverse.AgentManager.SitOnGround`. pub fn sit_on_ground(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.SitOnGround") + self.native_sit_on_ground() } /// C# member: `M:LibreMetaverse.AgentManager.SphereEffect(LibreMetaverse.Vector3d,LibreMetaverse.Color4,System.Single,LibreMetaverse.UUID)`. pub fn sphere_effect( @@ -1483,7 +1460,7 @@ impl AgentManager { } /// C# member: `M:LibreMetaverse.AgentManager.Stand`. pub fn stand(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.AgentManager.Stand") + self.native_stand() } /// C# member: `M:LibreMetaverse.AgentManager.StartIMConferenceAsync(System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID,System.Threading.CancellationToken)`. pub async fn start_im_conference( @@ -1501,9 +1478,8 @@ impl AgentManager { landmark: libremetaverse_types::UUID, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) + self.native_teleport_landmark(landmark, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.TeleportAsync(System.String,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)`. pub async fn teleport_with_string_vector3_vector3_cancellation_token( @@ -1513,9 +1489,8 @@ impl AgentManager { look_at: libremetaverse_types::Vector3, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportAsync(System.String,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)", - ) + self.native_teleport_name(sim_name, position, look_at, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.TeleportAsync(System.String,LibreMetaverse.Vector3,System.Threading.CancellationToken)`. pub async fn teleport_with_string_vector3_cancellation_token( @@ -1524,9 +1499,13 @@ impl AgentManager { position: libremetaverse_types::Vector3, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportAsync(System.String,LibreMetaverse.Vector3,System.Threading.CancellationToken)", + self.native_teleport_name( + sim_name, + position, + libremetaverse_types::Vector3::unit_y(), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)`. pub async fn teleport_with_u_int64_vector3_vector3_cancellation_token( @@ -1536,9 +1515,8 @@ impl AgentManager { look_at: libremetaverse_types::Vector3, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)", - ) + self.native_teleport_location(region_handle, position, look_at, cancellation_token) + .await } /// C# member: `M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,System.Threading.CancellationToken)`. pub async fn teleport_with_u_int64_vector3_cancellation_token( @@ -1547,9 +1525,13 @@ impl AgentManager { position: libremetaverse_types::Vector3, cancellation_token: Option, ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,System.Threading.CancellationToken)", + self.native_teleport_location( + region_handle, + position, + libremetaverse_types::Vector3::unit_y(), + cancellation_token, ) + .await } /// C# member: `M:LibreMetaverse.AgentManager.TeleportFailedEventHandler(System.String,LibreMetaverse.Interfaces.IMessage,LibreMetaverse.Simulator)`. pub fn teleport_failed_event_handler( @@ -1558,9 +1540,9 @@ impl AgentManager { message: Box, simulator: libremetaverse::Simulator, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportFailedEventHandler(System.String,LibreMetaverse.Interfaces.IMessage,LibreMetaverse.Simulator)", - ) + self.native_movement_runtime() + .handle_caps_event("TeleportFailed", message.as_ref(), simulator) + .map(|_| ()) } /// C# member: `M:LibreMetaverse.AgentManager.TeleportLureRespond(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)`. pub fn teleport_lure_respond( @@ -1569,9 +1551,7 @@ impl AgentManager { session_id: libremetaverse_types::UUID, accept: bool, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TeleportLureRespond(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)", - ) + self.native_teleport_lure_respond(requester_id, session_id, accept) } /// C# member: `M:LibreMetaverse.AgentManager.Touch(System.UInt32)`. pub fn touch(&self, object_local_id: u32) -> Result<(), crate::Error> { @@ -1583,9 +1563,7 @@ impl AgentManager { object_id: libremetaverse_types::UUID, sim: libremetaverse::Simulator, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.TrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)", - ) + Ok(self.native_track_object_in_simulator(object_id, sim)) } /// C# member: `M:LibreMetaverse.AgentManager.UntrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)`. pub fn untrack_object_in_simulator( @@ -1593,9 +1571,7 @@ impl AgentManager { object_id: libremetaverse_types::UUID, sim: libremetaverse::Simulator, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.UntrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)", - ) + Ok(self.native_untrack_object_in_simulator(object_id, &sim)) } /// C# member: `M:LibreMetaverse.AgentManager.UpdateAgentLanguageAsync(System.String,System.Boolean,System.Threading.CancellationToken)`. pub async fn update_agent_language( @@ -1730,7 +1706,7 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.Acceleration`. pub fn acceleration(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Acceleration") + self.native_movement_runtime().acceleration() } /// C# member: `P:LibreMetaverse.AgentManager.ActiveGestures`. pub fn active_gestures( @@ -1787,7 +1763,7 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.AngularVelocity`. pub fn angular_velocity(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AngularVelocity") + self.native_movement_runtime().angular_velocity() } /// C# member: `P:LibreMetaverse.AgentManager.AvatarRenderInfo`. pub fn avatar_render_info( @@ -1845,7 +1821,7 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.GlobalPosition`. pub fn global_position(&self) -> libremetaverse_types::Vector3d { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.GlobalPosition") + self.native_movement_runtime().global_position() } /// C# member: `P:LibreMetaverse.AgentManager.Health`. pub fn health(&self) -> f32 { @@ -1861,7 +1837,7 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.HomePosition`. pub fn home_position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.HomePosition") + self.native_movement_runtime().home_position() } /// C# member: `P:LibreMetaverse.AgentManager.HomeRegionHandle`. pub fn home_region_handle(&self) -> u64 { @@ -1898,11 +1874,12 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.LastPositionUpdate`. pub fn last_position_update(&self) -> std::time::SystemTime { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.LastPositionUpdate") + self.native_movement_runtime().last_position_update() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.LastPositionUpdate`. pub fn set_last_position_update(&mut self, value: std::time::SystemTime) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.LastPositionUpdate") + self.native_movement_runtime() + .set_last_position_update(value) } /// C# member: `P:LibreMetaverse.AgentManager.LastRegionExperiences`. pub fn last_region_experiences( @@ -1948,31 +1925,27 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.RelativePosition`. pub fn relative_position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.RelativePosition") + self.native_movement_runtime().relative_position() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.RelativePosition`. pub fn set_relative_position(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.RelativePosition") + self.native_movement_runtime().set_relative_position(value) } /// C# member: `P:LibreMetaverse.AgentManager.RelativePositionEstimate`. pub fn relative_position_estimate(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.RelativePositionEstimate" - ) + self.native_movement_runtime().relative_position_estimate() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.RelativePositionEstimate`. pub fn set_relative_position_estimate(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.RelativePositionEstimate" - ) + self.native_movement_runtime().set_relative_position(value) } /// C# member: `P:LibreMetaverse.AgentManager.RelativeRotation`. pub fn relative_rotation(&self) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.RelativeRotation") + self.native_movement_runtime().relative_rotation() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.RelativeRotation`. pub fn set_relative_rotation(&mut self, value: libremetaverse_types::Quaternion) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.RelativeRotation") + self.native_movement_runtime().set_relative_rotation(value) } /// C# member: `P:LibreMetaverse.AgentManager.SecureSessionID`. pub fn secure_session_id(&self) -> libremetaverse_types::UUID { @@ -1992,15 +1965,15 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.SimPosition`. pub fn sim_position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.SimPosition") + self.native_movement_runtime().sim_position() } /// C# member: `P:LibreMetaverse.AgentManager.SimRotation`. pub fn sim_rotation(&self) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.SimRotation") + self.native_movement_runtime().sim_rotation() } /// C# member: `P:LibreMetaverse.AgentManager.SittingOn`. pub fn sitting_on(&self) -> u32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.SittingOn") + self.native_movement_runtime().sitting_on() } /// C# member: `P:LibreMetaverse.AgentManager.StartLocation`. pub fn start_location(&self) -> String { @@ -2012,15 +1985,15 @@ impl AgentManager { } /// C# member: `P:LibreMetaverse.AgentManager.TeleportMessage`. pub fn teleport_message(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.TeleportMessage") + self.native_movement_runtime().teleport_message() } /// Setter for C# member: `P:LibreMetaverse.AgentManager.TeleportMessage`. pub fn set_teleport_message(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.TeleportMessage") + self.native_movement_runtime().set_teleport_message(value) } /// C# member: `P:LibreMetaverse.AgentManager.Velocity`. pub fn velocity(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.Velocity") + self.native_movement_runtime().velocity() } /// C# member: `P:LibreMetaverse.AgentManager.ViewerBenefits`. pub fn viewer_benefits( @@ -2038,653 +2011,75 @@ impl AgentManager { } /// C# type: `T:LibreMetaverse.AgentManager.AgentMovement`. -pub struct AgentManagerAgentMovement { - /// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.BodyRotation`. - pub body_rotation: libremetaverse_types::Quaternion, - /// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.Camera`. - pub camera: libremetaverse::AgentManagerAgentMovementAgentCamera, - /// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.Flags`. - pub flags: libremetaverse::AgentFlags, - /// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.HeadRotation`. - pub head_rotation: libremetaverse_types::Quaternion, - /// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.State`. - pub state: libremetaverse::AgentState, -} -impl AgentManagerAgentMovement { - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.ResetControlFlags`. - pub fn reset_control_flags(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.ResetControlFlags", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SendManualUpdate(LibreMetaverse.AgentManager.ControlFlags,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single,LibreMetaverse.AgentFlags,LibreMetaverse.AgentState,System.Boolean)`. - pub fn send_manual_update( - &self, - control_flags: libremetaverse::AgentManagerControlFlags, - position: libremetaverse_types::Vector3, - forward_axis: libremetaverse_types::Vector3, - left_axis: libremetaverse_types::Vector3, - up_axis: libremetaverse_types::Vector3, - body_rotation: libremetaverse_types::Quaternion, - head_rotation: libremetaverse_types::Quaternion, - far_clip: f32, - flags: libremetaverse::AgentFlags, - state: libremetaverse::AgentState, - reliable: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.SendManualUpdate(LibreMetaverse.AgentManager.ControlFlags,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single,LibreMetaverse.AgentFlags,LibreMetaverse.AgentState,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SendUpdate(System.Boolean)`. - pub fn send_update_with_boolean(&self, reliable: Option) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.SendUpdate(System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SendUpdate(System.Boolean,LibreMetaverse.Simulator)`. - pub fn send_update_with_boolean_simulator( - &self, - reliable: bool, - simulator: libremetaverse::Simulator, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.SendUpdate(System.Boolean,LibreMetaverse.Simulator)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SetFOVVerticalAngle(System.Single)`. - pub fn set_fov_vertical_angle(&self, angle: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.SetFOVVerticalAngle(System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.TurnToward(LibreMetaverse.Vector3,System.Boolean)`. - pub fn turn_toward( - &self, - target: libremetaverse_types::Vector3, - send_update: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.TurnToward(LibreMetaverse.Vector3,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.UpdateFromHeading(System.Double,System.Boolean)`. - pub fn update_from_heading(&self, heading: f64, reliable: bool) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.UpdateFromHeading(System.Double,System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentControls`. - pub fn agent_controls(&self) -> u32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentControls" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentControls`. - pub fn set_agent_controls(&mut self, value: u32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentControls" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AlwaysRun`. - pub fn always_run(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AlwaysRun" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AlwaysRun`. - pub fn set_always_run(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AlwaysRun" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AtNeg`. - pub fn at_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AtNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AtNeg`. - pub fn set_at_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AtNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AtPos`. - pub fn at_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AtPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AtPos`. - pub fn set_at_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AtPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AutoResetControls`. - pub fn auto_reset_controls(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AutoResetControls" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AutoResetControls`. - pub fn set_auto_reset_controls(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AutoResetControls" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Away`. - pub fn away(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentMovement.Away") - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Away`. - pub fn set_away(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentMovement.Away") - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastAt`. - pub fn fast_at(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FastAt" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastAt`. - pub fn set_fast_at(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FastAt" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastLeft`. - pub fn fast_left(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FastLeft" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastLeft`. - pub fn set_fast_left(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FastLeft" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastUp`. - pub fn fast_up(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FastUp" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastUp`. - pub fn set_fast_up(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FastUp" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FinishAnim`. - pub fn finish_anim(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FinishAnim" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FinishAnim`. - pub fn set_finish_anim(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.FinishAnim" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Fly`. - pub fn fly(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentMovement.Fly") - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Fly`. - pub fn set_fly(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentMovement.Fly") - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LButtonDown`. - pub fn l_button_down(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LButtonDown" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LButtonDown`. - pub fn set_l_button_down(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LButtonDown" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LButtonUp`. - pub fn l_button_up(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LButtonUp" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LButtonUp`. - pub fn set_l_button_up(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LButtonUp" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LeftNeg`. - pub fn left_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LeftNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LeftNeg`. - pub fn set_left_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LeftNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LeftPos`. - pub fn left_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LeftPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LeftPos`. - pub fn set_left_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.LeftPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.MLButtonDown`. - pub fn ml_button_down(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.MLButtonDown" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.MLButtonDown`. - pub fn set_ml_button_down(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.MLButtonDown" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.MLButtonUp`. - pub fn ml_button_up(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.MLButtonUp" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.MLButtonUp`. - pub fn set_ml_button_up(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.MLButtonUp" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Mouselook`. - pub fn mouselook(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.Mouselook" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Mouselook`. - pub fn set_mouselook(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.Mouselook" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtNeg`. - pub fn nudge_at_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtNeg`. - pub fn set_nudge_at_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtPos`. - pub fn nudge_at_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtPos`. - pub fn set_nudge_at_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftNeg`. - pub fn nudge_left_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftNeg`. - pub fn set_nudge_left_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftPos`. - pub fn nudge_left_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftPos`. - pub fn set_nudge_left_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpNeg`. - pub fn nudge_up_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpNeg`. - pub fn set_nudge_up_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpPos`. - pub fn nudge_up_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpPos`. - pub fn set_nudge_up_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.PitchNeg`. - pub fn pitch_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.PitchNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.PitchNeg`. - pub fn set_pitch_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.PitchNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.PitchPos`. - pub fn pitch_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.PitchPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.PitchPos`. - pub fn set_pitch_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.PitchPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.SitOnGround`. - pub fn sit_on_ground(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.SitOnGround" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.SitOnGround`. - pub fn set_sit_on_ground(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.SitOnGround" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.StandUp`. - pub fn stand_up(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.StandUp" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.StandUp`. - pub fn set_stand_up(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.StandUp" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Stop`. - pub fn stop(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentMovement.Stop") - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Stop`. - pub fn set_stop(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.AgentManager.AgentMovement.Stop") - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.TurnLeft`. - pub fn turn_left(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.TurnLeft" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.TurnLeft`. - pub fn set_turn_left(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.TurnLeft" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.TurnRight`. - pub fn turn_right(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.TurnRight" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.TurnRight`. - pub fn set_turn_right(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.TurnRight" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpNeg`. - pub fn up_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpNeg`. - pub fn set_up_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpPos`. - pub fn up_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpPos`. - pub fn set_up_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpPos" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpdateEnabled`. - pub fn update_enabled(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpdateEnabled" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpdateInterval`. - pub fn update_interval(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpdateInterval" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpdateInterval`. - pub fn set_update_interval(&mut self, value: i32) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.UpdateInterval" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.YawNeg`. - pub fn yaw_neg(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.YawNeg" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.YawNeg`. - pub fn set_yaw_neg(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.YawNeg" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.YawPos`. - pub fn yaw_pos(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.YawPos" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.YawPos`. - pub fn set_yaw_pos(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.YawPos" - ) - } -} +/// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.BodyRotation`. +/// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.Camera`. +/// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.Flags`. +/// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.HeadRotation`. +/// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.State`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.ResetControlFlags`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SendManualUpdate(LibreMetaverse.AgentManager.ControlFlags,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Quaternion,LibreMetaverse.Quaternion,System.Single,LibreMetaverse.AgentFlags,LibreMetaverse.AgentState,System.Boolean)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SendUpdate(System.Boolean)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SendUpdate(System.Boolean,LibreMetaverse.Simulator)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.SetFOVVerticalAngle(System.Single)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.TurnToward(LibreMetaverse.Vector3,System.Boolean)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.UpdateFromHeading(System.Double,System.Boolean)`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentControls`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AlwaysRun`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AtNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AtPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AutoResetControls`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Away`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastAt`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastLeft`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FastUp`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.FinishAnim`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Fly`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LButtonDown`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LButtonUp`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LeftNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.LeftPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.MLButtonDown`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.MLButtonUp`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Mouselook`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeAtPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeLeftPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.NudgeUpPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.PitchNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.PitchPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.SitOnGround`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.StandUp`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.Stop`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.TurnLeft`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.TurnRight`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpPos`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpdateEnabled`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.UpdateInterval`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.YawNeg`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.YawPos`. +pub use crate::agent_movement::AgentManagerAgentMovement; /// C# type: `T:LibreMetaverse.AgentManager.AgentMovement.AgentCamera`. -pub struct AgentManagerAgentMovementAgentCamera { - /// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Far`. - pub far: f32, -} -impl AgentManagerAgentMovementAgentCamera { - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.#ctor", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. - pub fn look_at_with_vector3_vector3( - &self, - position: libremetaverse_types::Vector3, - target: libremetaverse_types::Vector3, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. - pub fn look_at_with_vector3_vector3_vector3( - &self, - position: libremetaverse_types::Vector3, - target: libremetaverse_types::Vector3, - up_direction: libremetaverse_types::Vector3, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(LibreMetaverse.Vector3)`. - pub fn look_direction_with_vector3( - &self, - target: libremetaverse_types::Vector3, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. - pub fn look_direction_with_vector3_vector3( - &self, - target: libremetaverse_types::Vector3, - up_direction: libremetaverse_types::Vector3, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(LibreMetaverse.Vector3,LibreMetaverse.Vector3)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(System.Double)`. - pub fn look_direction_with_double(&self, heading: f64) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(System.Double)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Pitch(System.Single)`. - pub fn pitch(&self, angle: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Pitch(System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Roll(System.Single)`. - pub fn roll(&self, angle: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Roll(System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.SetPositionOrientation(LibreMetaverse.Vector3,System.Single,System.Single,System.Single)`. - pub fn set_position_orientation( - &self, - position: libremetaverse_types::Vector3, - roll: f32, - pitch: f32, - yaw: f32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.SetPositionOrientation(LibreMetaverse.Vector3,System.Single,System.Single,System.Single)", - ) - } - /// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Yaw(System.Single)`. - pub fn yaw(&self, angle: f32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Yaw(System.Single)", - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.AtAxis`. - pub fn at_axis(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.AtAxis" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.AtAxis`. - pub fn set_at_axis(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.AtAxis" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LeftAxis`. - pub fn left_axis(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LeftAxis" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LeftAxis`. - pub fn set_left_axis(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LeftAxis" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Position`. - pub fn position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Position" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Position`. - pub fn set_position(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Position" - ) - } - /// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.UpAxis`. - pub fn up_axis(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.UpAxis" - ) - } - /// Setter for C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.UpAxis`. - pub fn set_up_axis(&mut self, value: libremetaverse_types::Vector3) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.UpAxis" - ) - } -} +/// C# member: `F:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Far`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.#ctor`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookAt(LibreMetaverse.Vector3,LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(LibreMetaverse.Vector3,LibreMetaverse.Vector3)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LookDirection(System.Double)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Pitch(System.Single)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Roll(System.Single)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.SetPositionOrientation(LibreMetaverse.Vector3,System.Single,System.Single,System.Single)`. +/// C# member: `M:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Yaw(System.Single)`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.AtAxis`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.LeftAxis`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.Position`. +/// C# member: `P:LibreMetaverse.AgentManager.AgentMovement.AgentCamera.UpAxis`. +pub use crate::agent_movement::AgentManagerAgentMovementAgentCamera; /// C# type: `T:LibreMetaverse.AgentManager.ControlFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -5737,65 +5132,15 @@ impl AvatarSitChangedEventArgs { } /// C# type: `T:LibreMetaverse.AvatarSitResponseEventArgs`. -pub struct AvatarSitResponseEventArgs; -impl AvatarSitResponseEventArgs { - /// C# member: `M:LibreMetaverse.AvatarSitResponseEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean,LibreMetaverse.Vector3,LibreMetaverse.Quaternion)`. - pub fn new( - object_id: libremetaverse_types::UUID, - auto_pilot: bool, - camera_at_offset: libremetaverse_types::Vector3, - camera_eye_offset: libremetaverse_types::Vector3, - force_mouselook: bool, - sit_position: libremetaverse_types::Vector3, - sit_rotation: libremetaverse_types::Quaternion, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.AvatarSitResponseEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean,LibreMetaverse.Vector3,LibreMetaverse.Quaternion)", - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.Autopilot`. - pub fn autopilot(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.Autopilot" - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.CameraAtOffset`. - pub fn camera_at_offset(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.CameraAtOffset" - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.CameraEyeOffset`. - pub fn camera_eye_offset(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.CameraEyeOffset" - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.ForceMouselook`. - pub fn force_mouselook(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.ForceMouselook" - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.ObjectID`. - pub fn object_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.ObjectID" - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.SitPosition`. - pub fn sit_position(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.SitPosition" - ) - } - /// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.SitRotation`. - pub fn sit_rotation(&self) -> libremetaverse_types::Quaternion { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.AvatarSitResponseEventArgs.SitRotation" - ) - } -} +/// C# member: `M:LibreMetaverse.AvatarSitResponseEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean,LibreMetaverse.Vector3,LibreMetaverse.Quaternion)`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.Autopilot`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.CameraAtOffset`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.CameraEyeOffset`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.ForceMouselook`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.ObjectID`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.SitPosition`. +/// C# member: `P:LibreMetaverse.AvatarSitResponseEventArgs.SitRotation`. +pub use crate::agent_movement::AvatarSitResponseEventArgs; /// C# type: `T:LibreMetaverse.AvatarTextureIndex`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -6231,21 +5576,9 @@ impl CallingCardOfferedEventArgs { } /// C# type: `T:LibreMetaverse.CameraConstraintEventArgs`. -pub struct CameraConstraintEventArgs; -impl CameraConstraintEventArgs { - /// C# member: `M:LibreMetaverse.CameraConstraintEventArgs.#ctor(LibreMetaverse.Vector4)`. - pub fn new(collide_plane: libremetaverse_types::Vector4) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.CameraConstraintEventArgs.#ctor(LibreMetaverse.Vector4)", - ) - } - /// C# member: `P:LibreMetaverse.CameraConstraintEventArgs.CollidePlane`. - pub fn collide_plane(&self) -> libremetaverse_types::Vector4 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CameraConstraintEventArgs.CollidePlane" - ) - } -} +/// C# member: `M:LibreMetaverse.CameraConstraintEventArgs.#ctor(LibreMetaverse.Vector4)`. +/// C# member: `P:LibreMetaverse.CameraConstraintEventArgs.CollidePlane`. +pub use crate::agent_movement::CameraConstraintEventArgs; /// C# type: `T:LibreMetaverse.CapabilitiesReceivedEventArgs`. /// C# member: `M:LibreMetaverse.CapabilitiesReceivedEventArgs.#ctor(LibreMetaverse.Simulator)`. @@ -22282,63 +21615,17 @@ impl RebakeAvatarTexturesEventArgs { } /// C# type: `T:LibreMetaverse.RegionCrossedEventArgs`. -pub struct RegionCrossedEventArgs; -impl RegionCrossedEventArgs { - /// C# member: `M:LibreMetaverse.RegionCrossedEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.Simulator)`. - pub fn new( - old_sim: Option, - new_sim: Option, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.RegionCrossedEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.Simulator)", - ) - } - /// C# member: `P:LibreMetaverse.RegionCrossedEventArgs.NewSimulator`. - pub fn new_simulator(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionCrossedEventArgs.NewSimulator" - ) - } - /// C# member: `P:LibreMetaverse.RegionCrossedEventArgs.OldSimulator`. - pub fn old_simulator(&self) -> Option { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionCrossedEventArgs.OldSimulator" - ) - } -} +/// C# member: `M:LibreMetaverse.RegionCrossedEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.Simulator)`. +/// C# member: `P:LibreMetaverse.RegionCrossedEventArgs.NewSimulator`. +/// C# member: `P:LibreMetaverse.RegionCrossedEventArgs.OldSimulator`. +pub use crate::agent_movement::RegionCrossedEventArgs; /// C# type: `T:LibreMetaverse.RegionCrossingPredictionEventArgs`. -pub struct RegionCrossingPredictionEventArgs; -impl RegionCrossingPredictionEventArgs { - /// C# member: `M:LibreMetaverse.RegionCrossingPredictionEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.BorderCrossingDirection,System.Single)`. - pub fn new( - current_sim: libremetaverse::Simulator, - direction: libremetaverse::BorderCrossingDirection, - time_until_crossing: f32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.RegionCrossingPredictionEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.BorderCrossingDirection,System.Single)", - ) - } - /// C# member: `P:LibreMetaverse.RegionCrossingPredictionEventArgs.CurrentSimulator`. - pub fn current_simulator(&self) -> libremetaverse::Simulator { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionCrossingPredictionEventArgs.CurrentSimulator" - ) - } - /// C# member: `P:LibreMetaverse.RegionCrossingPredictionEventArgs.Direction`. - pub fn direction(&self) -> libremetaverse::BorderCrossingDirection { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionCrossingPredictionEventArgs.Direction" - ) - } - /// C# member: `P:LibreMetaverse.RegionCrossingPredictionEventArgs.TimeUntilCrossing`. - pub fn time_until_crossing(&self) -> f32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.RegionCrossingPredictionEventArgs.TimeUntilCrossing" - ) - } -} +/// C# member: `M:LibreMetaverse.RegionCrossingPredictionEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.BorderCrossingDirection,System.Single)`. +/// C# member: `P:LibreMetaverse.RegionCrossingPredictionEventArgs.CurrentSimulator`. +/// C# member: `P:LibreMetaverse.RegionCrossingPredictionEventArgs.Direction`. +/// C# member: `P:LibreMetaverse.RegionCrossingPredictionEventArgs.TimeUntilCrossing`. +pub use crate::agent_movement::RegionCrossingPredictionEventArgs; /// C# type: `T:LibreMetaverse.RegionEnvironmentEventArgs`. pub struct RegionEnvironmentEventArgs; @@ -24225,31 +23512,11 @@ impl TaskItemReceivedEventArgs { } /// C# type: `T:LibreMetaverse.TeleportEventArgs`. -pub struct TeleportEventArgs; -impl TeleportEventArgs { - /// C# member: `M:LibreMetaverse.TeleportEventArgs.#ctor(System.String,LibreMetaverse.TeleportStatus,LibreMetaverse.TeleportFlags)`. - pub fn new( - message: String, - status: libremetaverse::TeleportStatus, - flags: libremetaverse::TeleportFlags, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.TeleportEventArgs.#ctor(System.String,LibreMetaverse.TeleportStatus,LibreMetaverse.TeleportFlags)", - ) - } - /// C# member: `P:LibreMetaverse.TeleportEventArgs.Flags`. - pub fn flags(&self) -> libremetaverse::TeleportFlags { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.TeleportEventArgs.Flags") - } - /// C# member: `P:LibreMetaverse.TeleportEventArgs.Message`. - pub fn message(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.TeleportEventArgs.Message") - } - /// C# member: `P:LibreMetaverse.TeleportEventArgs.Status`. - pub fn status(&self) -> libremetaverse::TeleportStatus { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.TeleportEventArgs.Status") - } -} +/// C# member: `M:LibreMetaverse.TeleportEventArgs.#ctor(System.String,LibreMetaverse.TeleportStatus,LibreMetaverse.TeleportFlags)`. +/// C# member: `P:LibreMetaverse.TeleportEventArgs.Flags`. +/// C# member: `P:LibreMetaverse.TeleportEventArgs.Message`. +/// C# member: `P:LibreMetaverse.TeleportEventArgs.Status`. +pub use crate::agent_movement::TeleportEventArgs; /// C# type: `T:LibreMetaverse.TeleportFlags`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index 962f778..e355a92 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -6,6 +6,7 @@ extern crate self as libremetaverse; mod attention_catalog; mod agent_manager; mod agent_messages; +mod agent_movement; mod bit_pack; mod caps; mod caps_http; diff --git a/crates/libremetaverse/src/network_manager.rs b/crates/libremetaverse/src/network_manager.rs index 0813340..fd7fe1b 100644 --- a/crates/libremetaverse/src/network_manager.rs +++ b/crates/libremetaverse/src/network_manager.rs @@ -1011,6 +1011,7 @@ pub struct SimulatorData { pub wind_speeds: Option>, endpoint: std::net::SocketAddr, connected: AtomicBool, + movement_complete: AtomicBool, handshake_complete: AtomicBool, handshake_wait: Condvar, handshake_wait_lock: Mutex<()>, @@ -1173,6 +1174,7 @@ impl Simulator { wind_speeds: None, endpoint: address, connected: AtomicBool::new(false), + movement_complete: AtomicBool::new(false), handshake_complete: AtomicBool::new(false), handshake_wait: Condvar::new(), handshake_wait_lock: Mutex::new(()), @@ -1483,6 +1485,10 @@ impl Simulator { Ok(self.caps_running() || self.current_sim_caps_running()) } + pub(crate) fn native_start_event_queue(&self) { + self.start_current_event_queue(); + } + fn caps_running(&self) -> bool { read(&self.caps_state).as_ref().is_some_and(|inner| { Caps::native_from_inner(Arc::clone(inner), self.native_clone_without_caps()) @@ -1615,6 +1621,15 @@ impl Simulator { self.connected.load(Ordering::Acquire) } + #[must_use] + pub(crate) fn native_agent_movement_complete(&self) -> bool { + self.movement_complete.load(Ordering::Acquire) + } + + pub(crate) fn native_set_agent_movement_complete(&self, value: bool) { + self.movement_complete.store(value, Ordering::Release); + } + #[cfg(test)] pub(crate) fn native_set_connected_for_tests(&self, connected: bool) { self.connected.store(connected, Ordering::Release); diff --git a/docs/agent-movement.md b/docs/agent-movement.md new file mode 100644 index 0000000..2dc7f82 --- /dev/null +++ b/docs/agent-movement.md @@ -0,0 +1,65 @@ +# Agent movement, camera, teleport, and region crossing + +`AgentManager::movement` is backed by native Rust state and emits the same +LLUDP messages as LibreMetaverse's C# `AgentMovement`. The 32 control flags keep +their protocol bit positions. `ResetControlFlags` preserves away, fly, +mouselook, and crouch (`UP_NEG`), and automatic reset happens only after an +`AgentUpdate` was actually sent. Duplicate idle updates are limited to the same +ten transmissions as the reference implementation. + +The camera retains the reference `CoordinateFrame` behavior, including the +public wrapper's deliberate axis mapping: `AtAxis` exposes frame Y and +`LeftAxis` exposes frame X, while `LookDirection` writes the requested forward +vector to frame X. Roll, pitch, yaw, heading, look-at, and orthonormalization +are native floating-point operations. Non-finite inputs return the mapped +argument error instead of placing invalid values on the wire. + +## Update lifecycle + +The update worker starts after a successful login only when +`SendUpdatesRegularly` is enabled. It uses the configured +`AgentUpdateInterval`, wakes immediately when that interval changes, pauses on +disconnect, and is joined by `Dispose` or normal destruction. A condition variable +drives cadence and shutdown, so the implementation neither polls nor +uses arbitrary sleeps. Manual updates remain available when periodic updates +are disabled. Both paths refuse to send before simulator movement/handshake +completion, as required by their respective C# methods. + +Locomotion methods send real packets: flying, crouching, jumping, standing, +ground sitting, object sitting, always-run, FOV, and autopilot use +`AgentUpdate`, `AgentRequestSit`, `AgentSit`, `SetAlwaysRun`, `AgentFOV`, and +`GenericMessage` with the reference field layout and reliability choices. + +## Teleports and simulator crossings + +Landmark, location, named-region, and lure requests share a generation-tagged +teleport waiter. UDP `TeleportStart`, `TeleportProgress`, `TeleportFailed`, +`TeleportCancel`, `TeleportLocal`, and `TeleportFinish` packets, together with +their CAPS finish/failure equivalents, update status before raising +`TeleportProgress`. Finish, failure, cancellation, timeout, disposal, and a +superseding request all resolve the waiter exactly once. Cancellation and +timeouts are executor-driven and do not leave a task or callback registered. + +`CrossedRegion` starts a bounded native state machine with the C# states and +failure reasons. It connects the destination simulator, retries failed +connections up to three times, waits for that simulator's +`AgentMovementComplete`, restores the old simulator after rejection, timeout, +or manual cancellation, disconnects a partial destination, and raises +`RegionCrossed` after state mutation. `GetCrossingState`, +`GetCrossingFailureReason`, `GetCrossingDetails`, `IsCrossing`, and +`CancelCrossing` expose the same diagnostic surface. Crossing and periodic +update threads use weak ownership, cancellation wakeups, and joined handles. + +When `MultipleSims` is enabled, movement completion also snapshots presence, +position, rotation, and update time per simulator. Velocity near a region edge +raises `RegionCrossingPredicted` with the matching cardinal direction, and the +reference child-agent request bookkeeping is maintained with its 30-second +deduplication and two-minute expiry windows. Border-object visibility is +tracked through `TrackObjectInSimulator`, `UntrackObjectInSimulator`, and +`GetSimulatorsForObject`; disconnected simulator entries are pruned without +holding locks while callbacks run. + +Tests use paused Tokio time for teleport timeout/cancellation and loopback UDP +fake grids for packet-level movement. They decode the emitted control flags, +camera/FOV values, always-run state, and invariant-culture autopilot parameters +rather than accepting method invocation alone as evidence. diff --git a/tools/check_milestone_09_issue_60.py b/tools/check_milestone_09_issue_60.py new file mode 100644 index 0000000..73e241c --- /dev/null +++ b/tools/check_milestone_09_issue_60.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Audit issue 60's native movement, teleport, and crossing 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_movement.rs" +DOC = ROOT / "docs" / "agent-movement.md" +STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(") + +REQUIRED_TYPES = { + "T:LibreMetaverse.AgentManager.AgentMovement", + "T:LibreMetaverse.AgentManager.AgentMovement.AgentCamera", + "T:LibreMetaverse.AvatarSitResponseEventArgs", + "T:LibreMetaverse.CameraConstraintEventArgs", + "T:LibreMetaverse.RegionCrossedEventArgs", + "T:LibreMetaverse.RegionCrossingPredictionEventArgs", + "T:LibreMetaverse.TeleportEventArgs", +} + +REQUIRED_MEMBERS = { + "E:LibreMetaverse.AgentManager.AvatarSitResponse", + "E:LibreMetaverse.AgentManager.CameraConstraint", + "E:LibreMetaverse.AgentManager.RegionCrossed", + "E:LibreMetaverse.AgentManager.RegionCrossingPredicted", + "E:LibreMetaverse.AgentManager.TeleportProgress", + "M:LibreMetaverse.AgentManager.AutoPilot(System.Double,System.Double,System.Double)", + "M:LibreMetaverse.AgentManager.CancelCrossing", + "M:LibreMetaverse.AgentManager.CompleteAgentMovement(LibreMetaverse.Simulator)", + "M:LibreMetaverse.AgentManager.Crouch(System.Boolean)", + "M:LibreMetaverse.AgentManager.Fly(System.Boolean)", + "M:LibreMetaverse.AgentManager.GetCrossingDetails", + "M:LibreMetaverse.AgentManager.GetCrossingFailureReason", + "M:LibreMetaverse.AgentManager.GetCrossingState", + "M:LibreMetaverse.AgentManager.GetSimulatorsForObject(LibreMetaverse.UUID)", + "M:LibreMetaverse.AgentManager.IsCrossing", + "M:LibreMetaverse.AgentManager.IsNearBorder(LibreMetaverse.Vector3,System.UInt32,System.UInt32,System.Single)", + "M:LibreMetaverse.AgentManager.Jump(System.Boolean)", + "M:LibreMetaverse.AgentManager.RequestSit(LibreMetaverse.UUID,LibreMetaverse.Vector3)", + "M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean)", + "M:LibreMetaverse.AgentManager.Sit", + "M:LibreMetaverse.AgentManager.Stand", + "M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)", + "M:LibreMetaverse.AgentManager.TrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)", + "M:LibreMetaverse.AgentManager.UntrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)", +} + + +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: + missing_types = sorted(REQUIRED_TYPES - generate_api_shims.NATIVE_TYPES.keys()) + if missing_types: + raise SystemExit("issue 60 native types missing: " + ", ".join(missing_types)) + missing_members = sorted( + REQUIRED_MEMBERS - generate_api_shims.NATIVE_MEMBER_BODIES.keys() + ) + if missing_members: + raise SystemExit("issue 60 native members missing: " + ", ".join(missing_members)) + if STUB_RE.search(SOURCE.read_text()): + raise SystemExit("issue 60 owned Rust stubs remain in agent_movement.rs") + + require_markers( + SOURCE, + ( + "libremetaverse-agent-update", + "libremetaverse-region-crossing", + "TeleportLocal", + "AgentMovementComplete", + "fake_grid_decodes_locomotion_camera_and_autopilot_packets", + "fake_grids_complete_a_region_crossing_and_raise_the_final_event", + "multi_simulator_prediction_and_object_visibility_match_reference_rules", + "teleport_completion_timeout_and_cancellation_are_deterministic", + "camera_math_preserves_the_reference_frame_axis_mapping", + ), + ) + require_markers( + DOC, + ( + "ResetControlFlags", + "condition variable", + "generation-tagged", + "GetCrossingFailureReason", + "loopback UDP", + ), + ) + print( + "issue 60 audit: native movement/camera mappings, teleport and crossing " + "state machines, deterministic tests, documentation, and fake-grid wire " + "coverage are present" + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 0c1d32d..3bc5337 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -44,7 +44,10 @@ NATIVE_TYPES = { "T:LibreMetaverse.AgentAccessEventArgs": "crate::agent_messages::AgentAccessEventArgs", "T:LibreMetaverse.AgentExperiencesEventArgs": "crate::agent_messages::AgentExperiencesEventArgs", "T:LibreMetaverse.AgentPreferencesEventArgs": "crate::agent_messages::AgentPreferencesEventArgs", + "T:LibreMetaverse.AgentManager.AgentMovement": "crate::agent_movement::AgentManagerAgentMovement", + "T:LibreMetaverse.AgentManager.AgentMovement.AgentCamera": "crate::agent_movement::AgentManagerAgentMovementAgentCamera", "T:LibreMetaverse.AnimationsChangedEventArgs": "crate::agent_messages::AnimationsChangedEventArgs", + "T:LibreMetaverse.AvatarSitResponseEventArgs": "crate::agent_movement::AvatarSitResponseEventArgs", "T:LibreMetaverse.AvatarRenderInfoEventArgs": "crate::agent_messages::AvatarRenderInfoEventArgs", "T:LibreMetaverse.BalanceEventArgs": "crate::agent_messages::BalanceEventArgs", "T:LibreMetaverse.CapabilitiesReceivedEventArgs": "crate::caps::CapabilitiesReceivedEventArgs", @@ -64,6 +67,7 @@ NATIVE_TYPES = { "T:LibreMetaverse.ChatSessionMember": "crate::agent_messages::ChatSessionMember", "T:LibreMetaverse.ChatSessionMemberAddedEventArgs": "crate::agent_messages::ChatSessionMemberAddedEventArgs", "T:LibreMetaverse.ChatSessionMemberLeftEventArgs": "crate::agent_messages::ChatSessionMemberLeftEventArgs", + "T:LibreMetaverse.CameraConstraintEventArgs": "crate::agent_movement::CameraConstraintEventArgs", "T:LibreMetaverse.Assets.AssetGesture": "crate::gesture::AssetGesture", "T:LibreMetaverse.Assets.GestureStep": "crate::gesture::GestureStep", "T:LibreMetaverse.Assets.GestureStepAnimation": "crate::gesture::GestureStepAnimation", @@ -76,7 +80,10 @@ NATIVE_TYPES = { "T:LibreMetaverse.GroupChatJoinedEventArgs": "crate::agent_messages::GroupChatJoinedEventArgs", "T:LibreMetaverse.RegionExperiencesEventArgs": "crate::agent_messages::RegionExperiencesEventArgs", "T:LibreMetaverse.SetDisplayNameReplyEventArgs": "crate::agent_messages::SetDisplayNameReplyEventArgs", + "T:LibreMetaverse.TeleportEventArgs": "crate::agent_movement::TeleportEventArgs", "T:LibreMetaverse.ProductInfoEventArgs": "crate::agent_messages::ProductInfoEventArgs", + "T:LibreMetaverse.RegionCrossedEventArgs": "crate::agent_movement::RegionCrossedEventArgs", + "T:LibreMetaverse.RegionCrossingPredictionEventArgs": "crate::agent_movement::RegionCrossingPredictionEventArgs", "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", @@ -302,6 +309,10 @@ NATIVE_MEMBER_BODIES = { "self.native_subscribe_agent_data_reply(handler)", "E:LibreMetaverse.AgentManager.AnimationsChanged": "self.native_subscribe_animations_changed(handler)", + "E:LibreMetaverse.AgentManager.AvatarSitResponse": + "self.native_subscribe_avatar_sit_response(handler)", + "E:LibreMetaverse.AgentManager.CameraConstraint": + "self.native_subscribe_camera_constraint(handler)", "E:LibreMetaverse.AgentManager.ChatFromSimulator": "self.native_subscribe_chat_from_simulator(handler)", "E:LibreMetaverse.AgentManager.MoneyBalance": @@ -324,6 +335,10 @@ NATIVE_MEMBER_BODIES = { "self.native_subscribe_viewer_benefits_updated(handler)", "E:LibreMetaverse.AgentManager.ProductInfoUpdated": "self.native_subscribe_product_info_updated(handler)", + "E:LibreMetaverse.AgentManager.RegionCrossed": + "self.native_subscribe_region_crossed(handler)", + "E:LibreMetaverse.AgentManager.RegionCrossingPredicted": + "self.native_subscribe_region_crossing_predicted(handler)", "E:LibreMetaverse.AgentManager.MuteListUpdated": "self.native_subscribe_mute_list_updated(handler)", "E:LibreMetaverse.AgentManager.GroupChatJoined": @@ -336,6 +351,8 @@ NATIVE_MEMBER_BODIES = { "self.native_subscribe_set_display_name_reply(handler)", "E:LibreMetaverse.AgentManager.NavMeshStatusUpdate": "self.native_subscribe_nav_mesh_status_update(handler)", + "E:LibreMetaverse.AgentManager.TeleportProgress": + "self.native_subscribe_teleport_progress(handler)", "M:LibreMetaverse.AgentManager.#ctor(LibreMetaverse.GridClient)": "crate::agent_manager::AgentManager::native_new(client)", "M:LibreMetaverse.AgentManager.Dispose": @@ -348,18 +365,34 @@ NATIVE_MEMBER_BODIES = { "self.native_animation_start(animation, reliable)", "M:LibreMetaverse.AgentManager.AnimationStop(LibreMetaverse.UUID,System.Boolean)": "self.native_animation_stop(animation, reliable)", + "M:LibreMetaverse.AgentManager.AutoPilot(System.Double,System.Double,System.Double)": + "self.native_autopilot_f64(global_x, global_y, z)", + "M:LibreMetaverse.AgentManager.AutoPilot(System.UInt64,System.UInt64,System.Single)": + "self.native_autopilot_u64(global_x, global_y, z)", + "M:LibreMetaverse.AgentManager.AutoPilotCancel": + "self.native_autopilot_cancel()", + "M:LibreMetaverse.AgentManager.AutoPilotLocal(System.Int32,System.Int32,System.Single)": + "self.native_autopilot_local(local_x, local_y, z)", + "M:LibreMetaverse.AgentManager.CancelCrossing": + "Ok(self.native_cancel_crossing())", "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.CompleteAgentMovement(LibreMetaverse.Simulator)": + "self.native_complete_agent_movement(simulator)", + "M:LibreMetaverse.AgentManager.Crouch(System.Boolean)": + "self.native_crouch(crouching)", "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.Fly(System.Boolean)": + "self.native_fly(start)", "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)": @@ -376,6 +409,14 @@ NATIVE_MEMBER_BODIES = { "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.GetCrossingDetails": + "Ok(self.native_crossing_details())", + "M:LibreMetaverse.AgentManager.GetCrossingFailureReason": + "Ok(self.native_crossing_failure_reason())", + "M:LibreMetaverse.AgentManager.GetCrossingState": + "Ok(self.native_crossing_state())", + "M:LibreMetaverse.AgentManager.GetSimulatorsForObject(LibreMetaverse.UUID)": + "Ok(self.native_get_simulators_for_object(object_id))", "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)": @@ -390,6 +431,12 @@ NATIVE_MEMBER_BODIES = { "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.GoHomeAsync(System.Threading.CancellationToken)": + "self.native_teleport_landmark(libremetaverse_types::UUID::zero(), cancellation_token).await", + "M:LibreMetaverse.AgentManager.IsCrossing": + "Ok(self.native_is_crossing())", + "M:LibreMetaverse.AgentManager.IsNearBorder(LibreMetaverse.Vector3,System.UInt32,System.UInt32,System.Single)": + "Ok(self.native_is_near_border(position, region_size_x, region_size_y, threshold.unwrap_or(32.0)))", "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)": @@ -422,6 +469,8 @@ NATIVE_MEMBER_BODIES = { "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.Jump(System.Boolean)": + "self.native_jump(jumping)", "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)": @@ -454,6 +503,14 @@ NATIVE_MEMBER_BODIES = { "self.native_leave_group_chat(group_id)", "M:LibreMetaverse.AgentManager.RequestMuteList": "self.native_request_mute_list()", + "M:LibreMetaverse.AgentManager.RequestSit(LibreMetaverse.UUID,LibreMetaverse.Vector3)": + "self.native_request_sit(target_id, offset)", + "M:LibreMetaverse.AgentManager.RequestTeleport(LibreMetaverse.UUID)": + "self.native_request_teleport_landmark(landmark)", + "M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3)": + "self.native_request_teleport_location(region_handle, position, libremetaverse_types::Vector3::unit_y(), Some(false))", + "M:LibreMetaverse.AgentManager.RequestTeleport(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Boolean)": + "self.native_request_teleport_location(region_handle, position, look_at, ignore_caps_status)", "M:LibreMetaverse.AgentManager.RequestNavMeshGenerationStatusAsync(System.Threading.CancellationToken)": "self.native_request_nav_mesh_generation_status(cancellation_token).await", "M:LibreMetaverse.AgentManager.RequestNavMeshRebakeAsync(System.Threading.CancellationToken)": @@ -468,6 +525,16 @@ NATIVE_MEMBER_BODIES = { "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.SendTeleportLure(LibreMetaverse.UUID)": + "self.native_send_teleport_lure(target_id, None)", + "M:LibreMetaverse.AgentManager.SendTeleportLure(LibreMetaverse.UUID,System.String)": + "self.native_send_teleport_lure(target_id, Some(message))", + "M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID)": + "self.native_send_teleport_lure_request(target_id, target_id, \"Let me join you in your location\".into())", + "M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String)": + "self.native_send_teleport_lure_request(target_id, session_id, message)", + "M:LibreMetaverse.AgentManager.SendTeleportLureRequest(LibreMetaverse.UUID,System.String)": + "self.native_send_teleport_lure_request(target_id, target_id, message)", "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)": @@ -484,6 +551,30 @@ NATIVE_MEMBER_BODIES = { "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.Sit": + "self.native_sit()", + "M:LibreMetaverse.AgentManager.SitOnGround": + "self.native_sit_on_ground()", + "M:LibreMetaverse.AgentManager.Stand": + "self.native_stand()", + "M:LibreMetaverse.AgentManager.TeleportAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)": + "self.native_teleport_landmark(landmark, cancellation_token).await", + "M:LibreMetaverse.AgentManager.TeleportAsync(System.String,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)": + "self.native_teleport_name(sim_name, position, look_at, cancellation_token).await", + "M:LibreMetaverse.AgentManager.TeleportAsync(System.String,LibreMetaverse.Vector3,System.Threading.CancellationToken)": + "self.native_teleport_name(sim_name, position, libremetaverse_types::Vector3::unit_y(), cancellation_token).await", + "M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,LibreMetaverse.Vector3,System.Threading.CancellationToken)": + "self.native_teleport_location(region_handle, position, look_at, cancellation_token).await", + "M:LibreMetaverse.AgentManager.TeleportAsync(System.UInt64,LibreMetaverse.Vector3,System.Threading.CancellationToken)": + "self.native_teleport_location(region_handle, position, libremetaverse_types::Vector3::unit_y(), cancellation_token).await", + "M:LibreMetaverse.AgentManager.TeleportFailedEventHandler(System.String,LibreMetaverse.Interfaces.IMessage,LibreMetaverse.Simulator)": + "self.native_movement_runtime().handle_caps_event(\"TeleportFailed\", message.as_ref(), simulator).map(|_| ())", + "M:LibreMetaverse.AgentManager.TeleportLureRespond(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)": + "self.native_teleport_lure_respond(requester_id, session_id, accept)", + "M:LibreMetaverse.AgentManager.TrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)": + "Ok(self.native_track_object_in_simulator(object_id, sim))", + "M:LibreMetaverse.AgentManager.UntrackObjectInSimulator(LibreMetaverse.UUID,LibreMetaverse.Simulator)": + "Ok(self.native_untrack_object_in_simulator(object_id, &sim))", "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)": @@ -578,6 +669,42 @@ NATIVE_MEMBER_BODIES = { "self.native_session_id()", "P:LibreMetaverse.AgentManager.SessionID#set": "self.native_set_session_id(value)", + "P:LibreMetaverse.AgentManager.Acceleration": + "self.native_movement_runtime().acceleration()", + "P:LibreMetaverse.AgentManager.AngularVelocity": + "self.native_movement_runtime().angular_velocity()", + "P:LibreMetaverse.AgentManager.GlobalPosition": + "self.native_movement_runtime().global_position()", + "P:LibreMetaverse.AgentManager.HomePosition": + "self.native_movement_runtime().home_position()", + "P:LibreMetaverse.AgentManager.LastPositionUpdate": + "self.native_movement_runtime().last_position_update()", + "P:LibreMetaverse.AgentManager.LastPositionUpdate#set": + "self.native_movement_runtime().set_last_position_update(value)", + "P:LibreMetaverse.AgentManager.RelativePosition": + "self.native_movement_runtime().relative_position()", + "P:LibreMetaverse.AgentManager.RelativePosition#set": + "self.native_movement_runtime().set_relative_position(value)", + "P:LibreMetaverse.AgentManager.RelativePositionEstimate": + "self.native_movement_runtime().relative_position_estimate()", + "P:LibreMetaverse.AgentManager.RelativePositionEstimate#set": + "self.native_movement_runtime().set_relative_position(value)", + "P:LibreMetaverse.AgentManager.RelativeRotation": + "self.native_movement_runtime().relative_rotation()", + "P:LibreMetaverse.AgentManager.RelativeRotation#set": + "self.native_movement_runtime().set_relative_rotation(value)", + "P:LibreMetaverse.AgentManager.SimPosition": + "self.native_movement_runtime().sim_position()", + "P:LibreMetaverse.AgentManager.SimRotation": + "self.native_movement_runtime().sim_rotation()", + "P:LibreMetaverse.AgentManager.SittingOn": + "self.native_movement_runtime().sitting_on()", + "P:LibreMetaverse.AgentManager.TeleportMessage": + "self.native_movement_runtime().teleport_message()", + "P:LibreMetaverse.AgentManager.TeleportMessage#set": + "self.native_movement_runtime().set_teleport_message(value)", + "P:LibreMetaverse.AgentManager.Velocity": + "self.native_movement_runtime().velocity()", "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 2e32dee..c61b31b 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -984,6 +984,7 @@ def validate_generated_shims() -> None: "NotImplemented::new(", "crate::agent_manager::", "crate::agent_messages::", + "crate::agent_movement::", "crate::byte_order::", "crate::attention_catalog::", "crate::binary::",