/* * Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2025, Sjofn LLC. * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.co nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Threading; using OpenMetaverse.Packets; namespace OpenMetaverse { public partial class AgentManager { #region Enums /// /// Used to specify movement actions for your agent /// [Flags] public enum ControlFlags { /// Empty flag NONE = 0, /// Move Forward (SL Keybinding: W/Up Arrow) AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX, /// Move Backward (SL Keybinding: S/Down Arrow) AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX, /// Move Left (SL Keybinding: Shift-(A/Left Arrow)) AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX, /// Move Right (SL Keybinding: Shift-(D/Right Arrow)) AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX, /// Not Flying: Jump/Flying: Move Up (SL Keybinding: E) AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX, /// Not Flying: Croutch/Flying: Move Down (SL Keybinding: C) AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX, /// Unused AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX, /// Unused AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX, /// Unused AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX, /// Unused AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX, /// ORed with AGENT_CONTROL_AT_* if the keyboard is being used AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX, /// ORed with AGENT_CONTROL_LEFT_* if the keyboard is being used AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX, /// ORed with AGENT_CONTROL_UP_* if the keyboard is being used AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX, /// Fly AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX, /// AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX, /// Finish our current animation AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX, /// Stand up from the ground or a prim seat AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX, /// Sit on the ground at our current location AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX, /// Whether mouselook is currently enabled AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX, /// Legacy, used if a key was pressed for less than a certain amount of time AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX, /// AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX, /// AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX, /// Set when the avatar is idled or set to away. Note that the away animation is /// activated separately from setting this flag AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX, /// AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX, /// AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX, /// AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX, /// AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX } #endregion Enums #region AgentUpdate Constants private const int CONTROL_AT_POS_INDEX = 0; private const int CONTROL_AT_NEG_INDEX = 1; private const int CONTROL_LEFT_POS_INDEX = 2; private const int CONTROL_LEFT_NEG_INDEX = 3; private const int CONTROL_UP_POS_INDEX = 4; private const int CONTROL_UP_NEG_INDEX = 5; private const int CONTROL_PITCH_POS_INDEX = 6; private const int CONTROL_PITCH_NEG_INDEX = 7; private const int CONTROL_YAW_POS_INDEX = 8; private const int CONTROL_YAW_NEG_INDEX = 9; private const int CONTROL_FAST_AT_INDEX = 10; private const int CONTROL_FAST_LEFT_INDEX = 11; private const int CONTROL_FAST_UP_INDEX = 12; private const int CONTROL_FLY_INDEX = 13; private const int CONTROL_STOP_INDEX = 14; private const int CONTROL_FINISH_ANIM_INDEX = 15; private const int CONTROL_STAND_UP_INDEX = 16; private const int CONTROL_SIT_ON_GROUND_INDEX = 17; private const int CONTROL_MOUSELOOK_INDEX = 18; private const int CONTROL_NUDGE_AT_POS_INDEX = 19; private const int CONTROL_NUDGE_AT_NEG_INDEX = 20; private const int CONTROL_NUDGE_LEFT_POS_INDEX = 21; private const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22; private const int CONTROL_NUDGE_UP_POS_INDEX = 23; private const int CONTROL_NUDGE_UP_NEG_INDEX = 24; private const int CONTROL_TURN_LEFT_INDEX = 25; private const int CONTROL_TURN_RIGHT_INDEX = 26; private const int CONTROL_AWAY_INDEX = 27; private const int CONTROL_LBUTTON_DOWN_INDEX = 28; private const int CONTROL_LBUTTON_UP_INDEX = 29; private const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30; private const int CONTROL_ML_LBUTTON_UP_INDEX = 31; private const int TOTAL_CONTROLS = 32; #endregion AgentUpdate Constants /// /// Agent movement and camera control /// /// Agent movement is controlled by setting specific /// After the control flags are set, An AgentUpdate is required to update the simulator of the specified flags /// This is most easily accomplished by setting one or more of the AgentMovement properties /// /// Movement of an avatar is always based on a compass direction, for example AtPos will move the /// agent from West to East or forward on the X Axis, AtNeg will of course move agent from /// East to West or backward on the X Axis, LeftPos will be South to North or forward on the Y Axis /// The Z axis is Up, finer grained control of movements can be done using the Nudge properties /// public partial class AgentMovement { #region Properties /// Move agent positive along the X axis public bool AtPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_AT_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_AT_POS, value); } /// Move agent negative along the X axis public bool AtNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_AT_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_AT_NEG, value); } /// Move agent positive along the Y axis public bool LeftPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_LEFT_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_LEFT_POS, value); } /// Move agent negative along the Y axis public bool LeftNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_LEFT_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_LEFT_NEG, value); } /// Move agent positive along the Z axis public bool UpPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_UP_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_UP_POS, value); } /// Move agent negative along the Z axis public bool UpNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_UP_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_UP_NEG, value); } /// public bool PitchPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_PITCH_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_PITCH_POS, value); } /// public bool PitchNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_PITCH_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_PITCH_NEG, value); } /// public bool YawPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_YAW_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_YAW_POS, value); } /// public bool YawNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_YAW_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_YAW_NEG, value); } /// public bool FastAt { get => GetControlFlag(ControlFlags.AGENT_CONTROL_FAST_AT); set => SetControlFlag(ControlFlags.AGENT_CONTROL_FAST_AT, value); } /// public bool FastLeft { get => GetControlFlag(ControlFlags.AGENT_CONTROL_FAST_LEFT); set => SetControlFlag(ControlFlags.AGENT_CONTROL_FAST_LEFT, value); } /// public bool FastUp { get => GetControlFlag(ControlFlags.AGENT_CONTROL_FAST_UP); set => SetControlFlag(ControlFlags.AGENT_CONTROL_FAST_UP, value); } /// Causes simulator to make agent fly public bool Fly { get => GetControlFlag(ControlFlags.AGENT_CONTROL_FLY); set => SetControlFlag(ControlFlags.AGENT_CONTROL_FLY, value); } /// Stop movement public bool Stop { get => GetControlFlag(ControlFlags.AGENT_CONTROL_STOP); set => SetControlFlag(ControlFlags.AGENT_CONTROL_STOP, value); } /// Finish animation public bool FinishAnim { get => GetControlFlag(ControlFlags.AGENT_CONTROL_FINISH_ANIM); set => SetControlFlag(ControlFlags.AGENT_CONTROL_FINISH_ANIM, value); } /// Stand up from a sit public bool StandUp { get => GetControlFlag(ControlFlags.AGENT_CONTROL_STAND_UP); set => SetControlFlag(ControlFlags.AGENT_CONTROL_STAND_UP, value); } /// Tells simulator to sit agent on ground public bool SitOnGround { get => GetControlFlag(ControlFlags.AGENT_CONTROL_SIT_ON_GROUND); set => SetControlFlag(ControlFlags.AGENT_CONTROL_SIT_ON_GROUND, value); } /// Place agent into mouselook mode public bool Mouselook { get => GetControlFlag(ControlFlags.AGENT_CONTROL_MOUSELOOK); set => SetControlFlag(ControlFlags.AGENT_CONTROL_MOUSELOOK, value); } /// Nudge agent positive along the X axis public bool NudgeAtPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_AT_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_AT_POS, value); } /// Nudge agent negative along the X axis public bool NudgeAtNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_AT_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_AT_NEG, value); } /// Nudge agent positive along the Y axis public bool NudgeLeftPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_LEFT_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_LEFT_POS, value); } /// Nudge agent negative along the Y axis public bool NudgeLeftNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_LEFT_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_LEFT_NEG, value); } /// Nudge agent positive along the Z axis public bool NudgeUpPos { get => GetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_UP_POS); set => SetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_UP_POS, value); } /// Nudge agent negative along the Z axis public bool NudgeUpNeg { get => GetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG); set => SetControlFlag(ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG, value); } /// public bool TurnLeft { get => GetControlFlag(ControlFlags.AGENT_CONTROL_TURN_LEFT); set => SetControlFlag(ControlFlags.AGENT_CONTROL_TURN_LEFT, value); } /// public bool TurnRight { get => GetControlFlag(ControlFlags.AGENT_CONTROL_TURN_RIGHT); set => SetControlFlag(ControlFlags.AGENT_CONTROL_TURN_RIGHT, value); } /// Tell simulator to mark agent as away public bool Away { get => GetControlFlag(ControlFlags.AGENT_CONTROL_AWAY); set => SetControlFlag(ControlFlags.AGENT_CONTROL_AWAY, value); } /// public bool LButtonDown { get => GetControlFlag(ControlFlags.AGENT_CONTROL_LBUTTON_DOWN); set => SetControlFlag(ControlFlags.AGENT_CONTROL_LBUTTON_DOWN, value); } /// public bool LButtonUp { get => GetControlFlag(ControlFlags.AGENT_CONTROL_LBUTTON_UP); set => SetControlFlag(ControlFlags.AGENT_CONTROL_LBUTTON_UP, value); } /// public bool MLButtonDown { get => GetControlFlag(ControlFlags.AGENT_CONTROL_ML_LBUTTON_DOWN); set => SetControlFlag(ControlFlags.AGENT_CONTROL_ML_LBUTTON_DOWN, value); } /// public bool MLButtonUp { get => GetControlFlag(ControlFlags.AGENT_CONTROL_ML_LBUTTON_UP); set => SetControlFlag(ControlFlags.AGENT_CONTROL_ML_LBUTTON_UP, value); } /// /// Returns "always run" value, or changes it by sending a SetAlwaysRunPacket /// public bool AlwaysRun { get => alwaysRun; set { alwaysRun = value; SetAlwaysRunPacket run = new SetAlwaysRunPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, AlwaysRun = alwaysRun } }; Client.Network.SendPacket(run); } } /// The current value of the agent control flags public uint AgentControls { get; private set; } /// Gets or sets the interval in milliseconds at which /// AgentUpdate packets are sent to the current simulator. Setting /// this to a non-zero value will also enable the packet sending if /// it was previously off, and setting it to zero will disable public int UpdateInterval { get => updateInterval; set { if (value > 0) { updateTimer?.Change(value, value); updateInterval = value; } else { updateTimer?.Change(Timeout.Infinite, Timeout.Infinite); updateInterval = 0; } } } /// Gets or sets whether AgentUpdate packets are sent to /// the current simulator public bool UpdateEnabled => (updateInterval != 0); /// Reset movement controls every time we send an update public bool AutoResetControls { get; set; } #endregion Properties /// Agent camera controls public AgentCamera Camera; /// Currently only used for hiding your group title public AgentFlags Flags = AgentFlags.None; /// Action state of the avatar, which can currently be /// typing and editing public AgentState State = AgentState.None; /// public Quaternion BodyRotation = Quaternion.Identity; /// public Quaternion HeadRotation = Quaternion.Identity; #region Change tracking /// private Quaternion LastBodyRotation; /// private Quaternion LastHeadRotation; /// private Vector3 LastCameraCenter; /// private Vector3 LastCameraXAxis; /// private Vector3 LastCameraYAxis; /// private Vector3 LastCameraZAxis; /// private float LastFar; #endregion Change tracking private bool alwaysRun; private readonly GridClient Client; private int duplicateCount; private AgentState lastState; /// Timer for sending AgentUpdate packets private Timer updateTimer; private int updateInterval; /// Default constructor public AgentMovement(GridClient client) { Client = client; Camera = new AgentCamera(); Client.Network.LoginProgress += Network_OnConnected; Client.Network.Disconnected += Network_OnDisconnected; updateInterval = client.Settings.DEFAULT_AGENT_UPDATE_INTERVAL; } private void CleanupTimer() { updateTimer?.Dispose(); updateTimer = null; } private void Network_OnDisconnected(object sender, DisconnectedEventArgs e) { CleanupTimer(); } private void Network_OnConnected(object sender, LoginProgressEventArgs e) { if (e.Status == LoginStatus.Success) { CleanupTimer(); if (Client.Settings.SEND_AGENT_UPDATES_REGULARLY) { updateTimer = new Timer(UpdateTimer_Elapsed, null, updateInterval, updateInterval); } } } /// /// Send an AgentUpdate with the camera set at the current agent /// position and pointing towards the heading specified /// /// Camera rotation in radians /// Whether to send the AgentUpdate reliable /// or not public void UpdateFromHeading(double heading, bool reliable) { Camera.Position = Client.Self.SimPosition; Camera.LookDirection(heading); BodyRotation.Z = (float)Math.Sin(heading / 2.0d); BodyRotation.W = (float)Math.Cos(heading / 2.0d); HeadRotation = BodyRotation; SendUpdate(reliable); } /// /// Rotates the avatar body and camera toward a target position. /// This will also anchor the camera position on the avatar /// /// Region coordinates to turn toward /// whether to send update or not /// Returns if TurnToward operation was successful public bool TurnToward(Vector3 target, bool sendUpdate = true) { if (!Client.Settings.SEND_AGENT_UPDATES) { Logger.Log("Attempted TurnToward but agent updates are disabled", Helpers.LogLevel.Warning, Client); return false; } Quaternion parentRot = Quaternion.Identity; if (Client.Self.SittingOn > 0) { if (!Client.Network.CurrentSim.ObjectsPrimitives.TryGetValue(Client.Self.SittingOn, out var parent)) { Logger.Log("Attempted TurnToward but parent prim is not found", Helpers.LogLevel.Warning, Client); return false; } parentRot = parent.Rotation; } Quaternion between = Vector3.RotationBetween(Vector3.UnitX, Vector3.Normalize(target - Client.Self.SimPosition)); Quaternion rot = between * (Quaternion.Identity / parentRot); BodyRotation = rot; HeadRotation = rot; Camera.LookAt(Client.Self.SimPosition, target); if (sendUpdate) { SendUpdate(); } return true; } /// /// Send new AgentUpdate packet to update our current camera /// position and rotation /// /// Whether to require server acknowledgement /// of this packet public void SendUpdate(bool reliable = false) { SendUpdate(reliable, Client.Network.CurrentSim); } /// /// Send new AgentUpdate packet to update our current camera /// position and rotation /// /// Whether to require server acknowledgement /// of this packet /// Simulator to send the update to public void SendUpdate(bool reliable, Simulator simulator) { // Since version 1.40.4 of the Linden simulator, sending this update // causes corruption of the agent position in the simulator if (simulator != null && !simulator.AgentMovementComplete) { return; } Vector3 origin = Camera.Position; Vector3 xAxis = Camera.LeftAxis; Vector3 yAxis = Camera.AtAxis; Vector3 zAxis = Camera.UpAxis; // Attempted to sort these in a rough order of how often they might change if (AgentControls == 0 && yAxis == LastCameraYAxis && origin == LastCameraCenter && State == lastState && HeadRotation == LastHeadRotation && BodyRotation == LastBodyRotation && xAxis == LastCameraXAxis && Camera.Far == LastFar && zAxis == LastCameraZAxis) { ++duplicateCount; } else { duplicateCount = 0; } if (Client.Settings.DISABLE_AGENT_UPDATE_DUPLICATE_CHECK || duplicateCount < 10) { // Store the current state to do duplicate checking LastHeadRotation = HeadRotation; LastBodyRotation = BodyRotation; LastCameraYAxis = yAxis; LastCameraCenter = origin; LastCameraXAxis = xAxis; LastCameraZAxis = zAxis; LastFar = Camera.Far; lastState = State; // Build the AgentUpdate packet and send it AgentUpdatePacket update = new AgentUpdatePacket(); update.Header.Reliable = reliable; update.AgentData.AgentID = Client.Self.AgentID; update.AgentData.SessionID = Client.Self.SessionID; update.AgentData.HeadRotation = HeadRotation; update.AgentData.BodyRotation = BodyRotation; update.AgentData.CameraAtAxis = xAxis; update.AgentData.CameraCenter = origin; update.AgentData.CameraLeftAxis = yAxis; update.AgentData.CameraUpAxis = zAxis; update.AgentData.Far = Camera.Far; update.AgentData.State = (byte)State; update.AgentData.ControlFlags = AgentControls; update.AgentData.Flags = (byte)Flags; Client.Network.SendPacket(update, simulator); if (AutoResetControls) { ResetControlFlags(); } } } /// /// Builds an AgentUpdate packet entirely from parameters. This /// will not touch the state of Self.Movement or /// Self.Movement.Camera in any way /// /// /// /// /// /// /// /// /// /// /// /// public void SendManualUpdate(ControlFlags controlFlags, Vector3 position, Vector3 forwardAxis, Vector3 leftAxis, Vector3 upAxis, Quaternion bodyRotation, Quaternion headRotation, float farClip, AgentFlags flags, AgentState state, bool reliable) { // Since version 1.40.4 of the Linden simulator, sending this update // causes corruption of the agent position in the simulator if (Client.Network.CurrentSim != null && (!Client.Network.CurrentSim.HandshakeComplete)) return; AgentUpdatePacket update = new AgentUpdatePacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, BodyRotation = bodyRotation, HeadRotation = headRotation, CameraCenter = position, CameraAtAxis = forwardAxis, CameraLeftAxis = leftAxis, CameraUpAxis = upAxis, Far = farClip, ControlFlags = (uint)controlFlags, Flags = (byte)flags, State = (byte)state } }; update.Header.Reliable = reliable; Client.Network.SendPacket(update); } private bool GetControlFlag(ControlFlags flag) { return (AgentControls & (uint)flag) != 0; } private void SetControlFlag(ControlFlags flag, bool value) { if (value) AgentControls |= (uint)flag; else AgentControls &= ~((uint)flag); } public void ResetControlFlags() { // Reset all flags except for persistent settings like // away, fly, mouselook, and crouching AgentControls &= (uint)(ControlFlags.AGENT_CONTROL_AWAY | ControlFlags.AGENT_CONTROL_FLY | ControlFlags.AGENT_CONTROL_MOUSELOOK | ControlFlags.AGENT_CONTROL_UP_NEG); } /// /// Sends update of Field of Vision vertical angle to the simulator /// /// Angle in radians public void SetFOVVerticalAngle(float angle) { AgentFOVPacket msg = new AgentFOVPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, CircuitCode = Client.Network.CircuitCode }, FOVBlock = { GenCounter = 0, VerticalAngle = angle } }; Client.Network.SendPacket(msg); } private void UpdateTimer_Elapsed(object obj) { if (Client.Network.Connected && Client.Settings.SEND_AGENT_UPDATES) { //Send an AgentUpdate packet SendUpdate(false, Client.Network.CurrentSim); } } } } }