/* * Copyright (c) 2006-2014, openmetaverse.org * 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.org 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 System.Reflection; using System.Collections.Generic; using OpenMetaverse.Http; using OpenMetaverse.Packets; using OpenMetaverse.Interfaces; using OpenMetaverse.StructuredData; using OpenMetaverse.Messages.Linden; namespace OpenMetaverse { #region Enums /// /// Type of return to use when returning objects from a parcel /// public enum ObjectReturnType : uint { /// None = 0, /// Return objects owned by parcel owner Owner = 1 << 1, /// Return objects set to group Group = 1 << 2, /// Return objects not owned by parcel owner or set to group Other = 1 << 3, /// Return a specific list of objects on parcel List = 1 << 4, /// Return objects that are marked for-sale Sell = 1 << 5 } /// /// Blacklist/Whitelist flags used in parcels Access List /// public enum ParcelAccessFlags : uint { /// Agent is denied access NoAccess = 0, /// Agent is granted access Access = 1 } /// /// The result of a request for parcel properties /// public enum ParcelResult : int { /// No matches were found for the request NoData = -1, /// Request matched a single parcel Single = 0, /// Request matched multiple parcels Multiple = 1 } /// /// Flags used in the ParcelAccessListRequest packet to specify whether /// we want the access list (whitelist), ban list (blacklist), or both /// [Flags] public enum AccessList : uint { /// Request the access list Access = 1 << 0, /// Request the ban list Ban = 1 << 1, /// Request both White and Black lists Both = Access | Ban } /// /// Sequence ID in ParcelPropertiesReply packets (sent when avatar /// tries to cross a parcel border) /// public enum ParcelPropertiesStatus : int { /// Parcel is currently selected ParcelSelected = -10000, /// Parcel restricted to a group the avatar is not a /// member of CollisionNotInGroup = -20000, /// Avatar is banned from the parcel CollisionBanned = -30000, /// Parcel is restricted to an access list that the /// avatar is not on CollisionNotOnAccessList = -40000, /// Response to hovering over a parcel HoveredOverParcel = -50000 } /// /// The tool to use when modifying terrain levels /// public enum TerraformAction : byte { /// Level the terrain Level = 0, /// Raise the terrain Raise = 1, /// Lower the terrain Lower = 2, /// Smooth the terrain Smooth = 3, /// Add random noise to the terrain Noise = 4, /// Revert terrain to simulator default Revert = 5 } /// /// The tool size to use when changing terrain levels /// public enum TerraformBrushSize : byte { /// Small Small = 1, /// Medium Medium = 2, /// Large Large = 4 } /// /// Reasons agent is denied access to a parcel on the simulator /// public enum AccessDeniedReason : byte { /// Agent is not denied, access is granted NotDenied = 0, /// Agent is not a member of the group set for the parcel, or which owns the parcel NotInGroup = 1, /// Agent is not on the parcels specific allow list NotOnAllowList = 2, /// Agent is on the parcels ban list BannedFromParcel = 3, /// Unknown NoAccess = 4, /// Agent is not age verified and parcel settings deny access to non age verified avatars NotAgeVerified = 5 } /// /// Parcel overlay type. This is used primarily for highlighting and /// coloring which is why it is a single integer instead of a set of /// flags /// /// These values seem to be poorly thought out. The first three /// bits represent a single value, not flags. For example Auction (0x05) is /// not a combination of OwnedByOther (0x01) and ForSale(0x04). However, /// the BorderWest and BorderSouth values are bit flags that get attached /// to the value stored in the first three bits. Bits four, five, and six /// are unused [Flags] public enum ParcelOverlayType : byte { /// Public land Public = 0, /// Land is owned by another avatar OwnedByOther = 1, /// Land is owned by a group OwnedByGroup = 2, /// Land is owned by the current avatar OwnedBySelf = 3, /// Land is for sale ForSale = 4, /// Land is being auctioned Auction = 5, /// Land is private Private = 32, /// To the west of this area is a parcel border BorderWest = 64, /// To the south of this area is a parcel border BorderSouth = 128 } /// /// Various parcel properties /// [Flags] public enum ParcelFlags : uint { /// No flags set None = 0, /// Allow avatars to fly (a client-side only restriction) AllowFly = 1 << 0, /// Allow foreign scripts to run AllowOtherScripts = 1 << 1, /// This parcel is for sale ForSale = 1 << 2, /// Allow avatars to create a landmark on this parcel AllowLandmark = 1 << 3, /// Allows all avatars to edit the terrain on this parcel AllowTerraform = 1 << 4, /// Avatars have health and can take damage on this parcel. /// If set, avatars can be killed and sent home here AllowDamage = 1 << 5, /// Foreign avatars can create objects here CreateObjects = 1 << 6, /// All objects on this parcel can be purchased ForSaleObjects = 1 << 7, /// Access is restricted to a group UseAccessGroup = 1 << 8, /// Access is restricted to a whitelist UseAccessList = 1 << 9, /// Ban blacklist is enabled UseBanList = 1 << 10, /// Unknown UsePassList = 1 << 11, /// List this parcel in the search directory ShowDirectory = 1 << 12, /// Allow personally owned parcels to be deeded to group AllowDeedToGroup = 1 << 13, /// If Deeded, owner contributes required tier to group parcel is deeded to ContributeWithDeed = 1 << 14, /// Restrict sounds originating on this parcel to the /// parcel boundaries SoundLocal = 1 << 15, /// Objects on this parcel are sold when the land is /// purchsaed SellParcelObjects = 1 << 16, /// Allow this parcel to be published on the web AllowPublish = 1 << 17, /// The information for this parcel is mature content MaturePublish = 1 << 18, /// The media URL is an HTML page UrlWebPage = 1 << 19, /// The media URL is a raw HTML string UrlRawHtml = 1 << 20, /// Restrict foreign object pushes RestrictPushObject = 1 << 21, /// Ban all non identified/transacted avatars DenyAnonymous = 1 << 22, // Ban all identified avatars [OBSOLETE] //[Obsolete] // This was obsoleted in 1.19.0 but appears to be recycled and is used on linden homes parcels LindenHome = 1 << 23, // Ban all transacted avatars [OBSOLETE] //[Obsolete] //DenyTransacted = 1 << 24, /// Allow group-owned scripts to run AllowGroupScripts = 1 << 25, /// Allow object creation by group members or group /// objects CreateGroupObjects = 1 << 26, /// Allow all objects to enter this parcel AllowAPrimitiveEntry = 1 << 27, /// Only allow group and owner objects to enter this parcel AllowGroupObjectEntry = 1 << 28, /// Voice Enabled on this parcel AllowVoiceChat = 1 << 29, /// Use Estate Voice channel for Voice on this parcel UseEstateVoiceChan = 1 << 30, /// Deny Age Unverified Users DenyAgeUnverified = 1U << 31 } /// /// Parcel ownership status /// public enum ParcelStatus : sbyte { /// Placeholder None = -1, /// Parcel is leased (owned) by an avatar or group Leased = 0, /// Parcel is in process of being leased (purchased) by an avatar or group LeasePending = 1, /// Parcel has been abandoned back to Governor Linden Abandoned = 2 } /// /// Category parcel is listed in under search /// public enum ParcelCategory : sbyte { /// No assigned category None = 0, /// Linden Infohub or public area Linden, /// Adult themed area Adult, /// Arts and Culture Arts, /// Business Business, /// Educational Educational, /// Gaming Gaming, /// Hangout or Club Hangout, /// Newcomer friendly Newcomer, /// Parks and Nature Park, /// Residential Residential, /// Shopping Shopping, /// Not Used? Stage, /// Other Other, /// Not an actual category, only used for queries Any = -1 } /// /// Type of teleport landing for a parcel /// public enum LandingType : byte { /// Unset, simulator default None = 0, /// Specific landing point set for this parcel LandingPoint = 1, /// No landing point set, direct teleports enabled for /// this parcel Direct = 2 } /// /// Parcel Media Command used in ParcelMediaCommandMessage /// public enum ParcelMediaCommand : uint { /// Stop the media stream and go back to the first frame Stop = 0, /// Pause the media stream (stop playing but stay on current frame) Pause, /// Start the current media stream playing and stop when the end is reached Play, /// Start the current media stream playing, /// loop to the beginning when the end is reached and continue to play Loop, /// Specifies the texture to replace with video /// If passing the key of a texture, it must be explicitly typecast as a key, /// not just passed within double quotes. Texture, /// Specifies the movie URL (254 characters max) URL, /// Specifies the time index at which to begin playing Time, /// Specifies a single agent to apply the media command to Agent, /// Unloads the stream. While the stop command sets the texture to the first frame of the movie, /// unload resets it to the real texture that the movie was replacing. Unload, /// Turn on/off the auto align feature, similar to the auto align checkbox in the parcel media properties /// (NOT to be confused with the "align" function in the textures view of the editor!) Takes TRUE or FALSE as parameter. AutoAlign, /// Allows a Web page or image to be placed on a prim (1.19.1 RC0 and later only). /// Use "text/html" for HTML. Type, /// Resizes a Web page to fit on x, y pixels (1.19.1 RC0 and later only). /// This might still not be working Size, /// Sets a description for the media being displayed (1.19.1 RC0 and later only). Desc } #endregion Enums #region Structs /// /// Some information about a parcel of land returned from a DirectoryManager search /// public struct ParcelInfo { /// Global Key of record public UUID ID; /// Parcel Owners public UUID OwnerID; /// Name field of parcel, limited to 128 characters public string Name; /// Description field of parcel, limited to 256 characters public string Description; /// Total Square meters of parcel public int ActualArea; /// Total area billable as Tier, for group owned land this will be 10% less than ActualArea public int BillableArea; /// True of parcel is in Mature simulator public bool Mature; /// Grid global X position of parcel public float GlobalX; /// Grid global Y position of parcel public float GlobalY; /// Grid global Z position of parcel (not used) public float GlobalZ; /// Name of simulator parcel is located in public string SimName; /// Texture of parcels display picture public UUID SnapshotID; /// Float representing calculated traffic based on time spent on parcel by avatars public float Dwell; /// Sale price of parcel (not used) public int SalePrice; /// Auction ID of parcel public int AuctionID; } /// /// Parcel Media Information /// public struct ParcelMedia { /// A byte, if 0x1 viewer should auto scale media to fit object public bool MediaAutoScale; /// A boolean, if true the viewer should loop the media public bool MediaLoop; /// The Asset UUID of the Texture which when applied to a /// primitive will display the media public UUID MediaID; /// A URL which points to any Quicktime supported media type public string MediaURL; /// A description of the media public string MediaDesc; /// An Integer which represents the height of the media public int MediaHeight; /// An integer which represents the width of the media public int MediaWidth; /// A string which contains the mime type of the media public string MediaType; } #endregion Structs #region Parcel Class /// /// Parcel of land, a portion of virtual real estate in a simulator /// public class Parcel { /// The total number of contiguous 4x4 meter blocks your agent owns within this parcel public int SelfCount; /// The total number of contiguous 4x4 meter blocks contained in this parcel owned by a group or agent other than your own public int OtherCount; /// Deprecated, Value appears to always be 0 public int PublicCount; /// Simulator-local ID of this parcel public int LocalID; /// UUID of the owner of this parcel public UUID OwnerID; /// Whether the land is deeded to a group or not public bool IsGroupOwned; /// public uint AuctionID; /// Date land was claimed public DateTime ClaimDate; /// Appears to always be zero public int ClaimPrice; /// This field is no longer used public int RentPrice; /// Minimum corner of the axis-aligned bounding box for this /// parcel public Vector3 AABBMin; /// Maximum corner of the axis-aligned bounding box for this /// parcel public Vector3 AABBMax; /// Bitmap describing land layout in 4x4m squares across the /// entire region public byte[] Bitmap; /// Total parcel land area public int Area; /// public ParcelStatus Status; /// Maximum primitives across the entire simulator owned by the same agent or group that owns this parcel that can be used public int SimWideMaxPrims; /// Total primitives across the entire simulator calculated by combining the allowed prim counts for each parcel /// owned by the agent or group that owns this parcel public int SimWideTotalPrims; /// Maximum number of primitives this parcel supports public int MaxPrims; /// Total number of primitives on this parcel public int TotalPrims; /// For group-owned parcels this indicates the total number of prims deeded to the group, /// for parcels owned by an individual this inicates the number of prims owned by the individual public int OwnerPrims; /// Total number of primitives owned by the parcel group on /// this parcel, or for parcels owned by an individual with a group set the /// total number of prims set to that group. public int GroupPrims; /// Total number of prims owned by other avatars that are not set to group, or not the parcel owner public int OtherPrims; /// A bonus multiplier which allows parcel prim counts to go over times this amount, this does not affect /// the max prims per simulator. e.g: 117 prim parcel limit x 1.5 bonus = 175 allowed public float ParcelPrimBonus; /// Autoreturn value in minutes for others' objects public int OtherCleanTime; /// public ParcelFlags Flags; /// Sale price of the parcel, only useful if ForSale is set /// The SalePrice will remain the same after an ownership /// transfer (sale), so it can be used to see the purchase price after /// a sale if the new owner has not changed it public int SalePrice; /// Parcel Name public string Name; /// Parcel Description public string Desc; /// URL For Music Stream public string MusicURL; /// public UUID GroupID; /// Price for a temporary pass public int PassPrice; /// How long is pass valid for public float PassHours; /// public ParcelCategory Category; /// Key of authorized buyer public UUID AuthBuyerID; /// Key of parcel snapshot public UUID SnapshotID; /// The landing point location public Vector3 UserLocation; /// The landing point LookAt public Vector3 UserLookAt; /// The type of landing enforced from the enum public LandingType Landing; /// public float Dwell; /// public bool RegionDenyAnonymous; /// public bool RegionPushOverride; /// Access list of who is whitelisted on this /// parcel public List AccessWhiteList; /// Access list of who is blacklisted on this /// parcel public List AccessBlackList; /// TRUE of region denies access to age unverified users public bool RegionDenyAgeUnverified; /// true to obscure (hide) media url public bool ObscureMedia; /// true to obscure (hide) music url public bool ObscureMusic; /// A struct containing media details public ParcelMedia Media; /// true if avatars in this parcel should be invisible to people outside public bool SeeAVs; /// true if avatars outside can hear any sounds avatars inside play public bool AnyAVSounds; /// true if group members outside can hear any sounds avatars inside play public bool GroupAVSounds; /// /// Displays a parcel object in string format /// /// string containing key=value pairs of a parcel object public override string ToString() { string result = ""; Type parcelType = this.GetType(); FieldInfo[] fields = parcelType.GetFields(); foreach (FieldInfo field in fields) { result += (field.Name + " = " + field.GetValue(this) + " "); } return result; } /// /// Defalt constructor /// /// Local ID of this parcel public Parcel(int localID) { LocalID = localID; ClaimDate = Utils.Epoch; Bitmap = Utils.EmptyBytes; Name = String.Empty; Desc = String.Empty; MusicURL = String.Empty; AccessWhiteList = new List(0); AccessBlackList = new List(0); Media = new ParcelMedia(); } /// /// Update the simulator with any local changes to this Parcel object /// /// Simulator to send updates to /// Whether we want the simulator to confirm /// the update with a reply packet or not public void Update(Simulator simulator, bool wantReply) { Uri url = simulator.Caps.CapabilityURI("ParcelPropertiesUpdate"); if (url != null) { ParcelPropertiesUpdateMessage req = new ParcelPropertiesUpdateMessage(); req.AuthBuyerID = this.AuthBuyerID; req.Category = this.Category; req.Desc = this.Desc; req.GroupID = this.GroupID; req.Landing = this.Landing; req.LocalID = this.LocalID; req.MediaAutoScale = this.Media.MediaAutoScale; req.MediaDesc = this.Media.MediaDesc; req.MediaHeight = this.Media.MediaHeight; req.MediaID = this.Media.MediaID; req.MediaLoop = this.Media.MediaLoop; req.MediaType = this.Media.MediaType; req.MediaURL = this.Media.MediaURL; req.MediaWidth = this.Media.MediaWidth; req.MusicURL = this.MusicURL; req.Name = this.Name; req.ObscureMedia = this.ObscureMedia; req.ObscureMusic = this.ObscureMusic; req.ParcelFlags = this.Flags; req.PassHours = this.PassHours; req.PassPrice = (uint)this.PassPrice; req.SalePrice = (uint)this.SalePrice; req.SnapshotID = this.SnapshotID; req.UserLocation = this.UserLocation; req.UserLookAt = this.UserLookAt; req.SeeAVs = this.SeeAVs; req.AnyAVSounds = this.AnyAVSounds; req.GroupAVSounds = this.GroupAVSounds; OSDMap body = req.Serialize(); CapsClient capsPost = new CapsClient(url); capsPost.BeginGetResponse(body, OSDFormat.Xml, simulator.Client.Settings.CAPS_TIMEOUT); } else { ParcelPropertiesUpdatePacket request = new ParcelPropertiesUpdatePacket(); request.AgentData.AgentID = simulator.Client.Self.AgentID; request.AgentData.SessionID = simulator.Client.Self.SessionID; request.ParcelData.LocalID = this.LocalID; request.ParcelData.AuthBuyerID = this.AuthBuyerID; request.ParcelData.Category = (byte)this.Category; request.ParcelData.Desc = Utils.StringToBytes(this.Desc); request.ParcelData.GroupID = this.GroupID; request.ParcelData.LandingType = (byte)this.Landing; request.ParcelData.MediaAutoScale = (this.Media.MediaAutoScale) ? (byte)0x1 : (byte)0x0; request.ParcelData.MediaID = this.Media.MediaID; request.ParcelData.MediaURL = Utils.StringToBytes(this.Media.MediaURL.ToString()); request.ParcelData.MusicURL = Utils.StringToBytes(this.MusicURL.ToString()); request.ParcelData.Name = Utils.StringToBytes(this.Name); if (wantReply) request.ParcelData.Flags = 1; request.ParcelData.ParcelFlags = (uint)this.Flags; request.ParcelData.PassHours = this.PassHours; request.ParcelData.PassPrice = this.PassPrice; request.ParcelData.SalePrice = this.SalePrice; request.ParcelData.SnapshotID = this.SnapshotID; request.ParcelData.UserLocation = this.UserLocation; request.ParcelData.UserLookAt = this.UserLookAt; simulator.SendPacket(request); } UpdateOtherCleanTime(simulator); } /// /// Set Autoreturn time /// /// Simulator to send the update to public void UpdateOtherCleanTime(Simulator simulator) { ParcelSetOtherCleanTimePacket request = new ParcelSetOtherCleanTimePacket(); request.AgentData.AgentID = simulator.Client.Self.AgentID; request.AgentData.SessionID = simulator.Client.Self.SessionID; request.ParcelData.LocalID = this.LocalID; request.ParcelData.OtherCleanTime = this.OtherCleanTime; simulator.SendPacket(request); } } #endregion Parcel Class /// /// Parcel (subdivided simulator lots) subsystem /// public class ParcelManager { #region Structs /// /// Parcel Accesslist /// public struct ParcelAccessEntry { /// Agents public UUID AgentID; /// public DateTime Time; /// Flags for specific entry in white/black lists public AccessList Flags; } /// /// Owners of primitives on parcel /// public struct ParcelPrimOwners { /// Prim Owners public UUID OwnerID; /// True of owner is group public bool IsGroupOwned; /// Total count of prims owned by OwnerID public int Count; /// true of OwnerID is currently online and is not a group public bool OnlineStatus; /// The date of the most recent prim left by OwnerID public DateTime NewestPrim; } #endregion Structs #region Delegates /// /// Called once parcel resource usage information has been collected /// /// Indicates if operation was successfull /// Parcel resource usage information public delegate void LandResourcesCallback(bool success, LandResourcesInfo info); /// The event subscribers. null if no subcribers private EventHandler m_DwellReply; /// Raises the ParcelDwellReply event /// A ParcelDwellReplyEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelDwellReply(ParcelDwellReplyEventArgs e) { EventHandler handler = m_DwellReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_DwellReplyLock = new object(); /// Raised when the simulator responds to a request public event EventHandler ParcelDwellReply { add { lock (m_DwellReplyLock) { m_DwellReply += value; } } remove { lock (m_DwellReplyLock) { m_DwellReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ParcelInfo; /// Raises the ParcelInfoReply event /// A ParcelInfoReplyEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelInfoReply(ParcelInfoReplyEventArgs e) { EventHandler handler = m_ParcelInfo; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ParcelInfoLock = new object(); /// Raised when the simulator responds to a request public event EventHandler ParcelInfoReply { add { lock (m_ParcelInfoLock) { m_ParcelInfo += value; } } remove { lock (m_ParcelInfoLock) { m_ParcelInfo -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ParcelProperties; /// Raises the ParcelProperties event /// A ParcelPropertiesEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelProperties(ParcelPropertiesEventArgs e) { EventHandler handler = m_ParcelProperties; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ParcelPropertiesLock = new object(); /// Raised when the simulator responds to a request public event EventHandler ParcelProperties { add { lock (m_ParcelPropertiesLock) { m_ParcelProperties += value; } } remove { lock (m_ParcelPropertiesLock) { m_ParcelProperties -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ParcelACL; /// Raises the ParcelAccessListReply event /// A ParcelAccessListReplyEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelAccessListReply(ParcelAccessListReplyEventArgs e) { EventHandler handler = m_ParcelACL; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ParcelACLLock = new object(); /// Raised when the simulator responds to a request public event EventHandler ParcelAccessListReply { add { lock (m_ParcelACLLock) { m_ParcelACL += value; } } remove { lock (m_ParcelACLLock) { m_ParcelACL -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ParcelObjectOwnersReply; /// Raises the ParcelObjectOwnersReply event /// A ParcelObjectOwnersReplyEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelObjectOwnersReply(ParcelObjectOwnersReplyEventArgs e) { EventHandler handler = m_ParcelObjectOwnersReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ParcelObjectOwnersLock = new object(); /// Raised when the simulator responds to a request public event EventHandler ParcelObjectOwnersReply { add { lock (m_ParcelObjectOwnersLock) { m_ParcelObjectOwnersReply += value; } } remove { lock (m_ParcelObjectOwnersLock) { m_ParcelObjectOwnersReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_SimParcelsDownloaded; /// Raises the SimParcelsDownloaded event /// A SimParcelsDownloadedEventArgs object containing the /// data returned from the simulator protected virtual void OnSimParcelsDownloaded(SimParcelsDownloadedEventArgs e) { EventHandler handler = m_SimParcelsDownloaded; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_SimParcelsDownloadedLock = new object(); /// Raised when the simulator responds to a request public event EventHandler SimParcelsDownloaded { add { lock (m_SimParcelsDownloadedLock) { m_SimParcelsDownloaded += value; } } remove { lock (m_SimParcelsDownloadedLock) { m_SimParcelsDownloaded -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ForceSelectObjects; /// Raises the ForceSelectObjectsReply event /// A ForceSelectObjectsReplyEventArgs object containing the /// data returned from the simulator protected virtual void OnForceSelectObjectsReply(ForceSelectObjectsReplyEventArgs e) { EventHandler handler = m_ForceSelectObjects; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ForceSelectObjectsLock = new object(); /// Raised when the simulator responds to a request public event EventHandler ForceSelectObjectsReply { add { lock (m_ForceSelectObjectsLock) { m_ForceSelectObjects += value; } } remove { lock (m_ForceSelectObjectsLock) { m_ForceSelectObjects -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ParcelMediaUpdateReply; /// Raises the ParcelMediaUpdateReply event /// A ParcelMediaUpdateReplyEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelMediaUpdateReply(ParcelMediaUpdateReplyEventArgs e) { EventHandler handler = m_ParcelMediaUpdateReply; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ParcelMediaUpdateReplyLock = new object(); /// Raised when the simulator responds to a Parcel Update request public event EventHandler ParcelMediaUpdateReply { add { lock (m_ParcelMediaUpdateReplyLock) { m_ParcelMediaUpdateReply += value; } } remove { lock (m_ParcelMediaUpdateReplyLock) { m_ParcelMediaUpdateReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_ParcelMediaCommand; /// Raises the ParcelMediaCommand event /// A ParcelMediaCommandEventArgs object containing the /// data returned from the simulator protected virtual void OnParcelMediaCommand(ParcelMediaCommandEventArgs e) { EventHandler handler = m_ParcelMediaCommand; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ParcelMediaCommandLock = new object(); /// Raised when the parcel your agent is located sends a ParcelMediaCommand public event EventHandler ParcelMediaCommand { add { lock (m_ParcelMediaCommandLock) { m_ParcelMediaCommand += value; } } remove { lock (m_ParcelMediaCommandLock) { m_ParcelMediaCommand -= value; } } } #endregion Delegates private GridClient Client; private AutoResetEvent WaitForSimParcel; #region Public Methods /// /// Default constructor /// /// A reference to the GridClient object public ParcelManager(GridClient client) { Client = client; // Setup the callbacks Client.Network.RegisterCallback(PacketType.ParcelInfoReply, ParcelInfoReplyHandler); Client.Network.RegisterEventCallback("ParcelObjectOwnersReply", new Caps.EventQueueCallback(ParcelObjectOwnersReplyHandler)); // CAPS packet handler, to allow for Media Data not contained in the message template Client.Network.RegisterEventCallback("ParcelProperties", new Caps.EventQueueCallback(ParcelPropertiesReplyHandler)); Client.Network.RegisterCallback(PacketType.ParcelDwellReply, ParcelDwellReplyHandler); Client.Network.RegisterCallback(PacketType.ParcelAccessListReply, ParcelAccessListReplyHandler); Client.Network.RegisterCallback(PacketType.ForceObjectSelect, SelectParcelObjectsReplyHandler); Client.Network.RegisterCallback(PacketType.ParcelMediaUpdate, ParcelMediaUpdateHandler); Client.Network.RegisterCallback(PacketType.ParcelOverlay, ParcelOverlayHandler); Client.Network.RegisterCallback(PacketType.ParcelMediaCommandMessage, ParcelMediaCommandMessagePacketHandler); } /// /// Request basic information for a single parcel /// /// Simulator-local ID of the parcel public void RequestParcelInfo(UUID parcelID) { ParcelInfoRequestPacket request = new ParcelInfoRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Data.ParcelID = parcelID; Client.Network.SendPacket(request); } /// /// Request properties of a single parcel /// /// Simulator containing the parcel /// Simulator-local ID of the parcel /// An arbitrary integer that will be returned /// with the ParcelProperties reply, useful for distinguishing between /// multiple simultaneous requests public void RequestParcelProperties(Simulator simulator, int localID, int sequenceID) { ParcelPropertiesRequestByIDPacket request = new ParcelPropertiesRequestByIDPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ParcelData.LocalID = localID; request.ParcelData.SequenceID = sequenceID; Client.Network.SendPacket(request, simulator); } /// /// Request the access list for a single parcel /// /// Simulator containing the parcel /// Simulator-local ID of the parcel /// An arbitrary integer that will be returned /// with the ParcelAccessList reply, useful for distinguishing between /// multiple simultaneous requests /// public void RequestParcelAccessList(Simulator simulator, int localID, AccessList flags, int sequenceID) { ParcelAccessListRequestPacket request = new ParcelAccessListRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Data.LocalID = localID; request.Data.Flags = (uint)flags; request.Data.SequenceID = sequenceID; Client.Network.SendPacket(request, simulator); } /// /// Request properties of parcels using a bounding box selection /// /// Simulator containing the parcel /// Northern boundary of the parcel selection /// Eastern boundary of the parcel selection /// Southern boundary of the parcel selection /// Western boundary of the parcel selection /// An arbitrary integer that will be returned /// with the ParcelProperties reply, useful for distinguishing between /// different types of parcel property requests /// A boolean that is returned with the /// ParcelProperties reply, useful for snapping focus to a single /// parcel public void RequestParcelProperties(Simulator simulator, float north, float east, float south, float west, int sequenceID, bool snapSelection) { ParcelPropertiesRequestPacket request = new ParcelPropertiesRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ParcelData.North = north; request.ParcelData.East = east; request.ParcelData.South = south; request.ParcelData.West = west; request.ParcelData.SequenceID = sequenceID; request.ParcelData.SnapSelection = snapSelection; Client.Network.SendPacket(request, simulator); } /// /// Request all simulator parcel properties (used for populating the Simulator.Parcels /// dictionary) /// /// Simulator to request parcels from (must be connected) public void RequestAllSimParcels(Simulator simulator) { RequestAllSimParcels(simulator, false, 750); } /// /// Request all simulator parcel properties (used for populating the Simulator.Parcels /// dictionary) /// /// Simulator to request parcels from (must be connected) /// If TRUE, will force a full refresh /// Number of milliseconds to pause in between each request public void RequestAllSimParcels(Simulator simulator, bool refresh, int msDelay) { if (simulator.DownloadingParcelMap) { Logger.Log("Already downloading parcels in " + simulator.Name, Helpers.LogLevel.Info, Client); return; } else { simulator.DownloadingParcelMap = true; WaitForSimParcel = new AutoResetEvent(false); } if (refresh) { for (int y = 0; y < 64; y++) for (int x = 0; x < 64; x++) simulator.ParcelMap[y, x] = 0; } Thread th = new Thread(delegate() { int count = 0, timeouts = 0, y, x; for (y = 0; y < 64; y++) { for (x = 0; x < 64; x++) { if (!Client.Network.Connected) return; if (simulator.ParcelMap[y, x] == 0) { Client.Parcels.RequestParcelProperties(simulator, (y + 1) * 4.0f, (x + 1) * 4.0f, y * 4.0f, x * 4.0f, int.MaxValue, false); // Wait the given amount of time for a reply before sending the next request if (!WaitForSimParcel.WaitOne(msDelay, false)) ++timeouts; ++count; } } } Logger.Log(String.Format( "Full simulator parcel information retrieved. Sent {0} parcel requests. Current outgoing queue: {1}, Retry Count {2}", count, Client.Network.OutboxCount, timeouts), Helpers.LogLevel.Info, Client); simulator.DownloadingParcelMap = false; }); th.Start(); } /// /// Request the dwell value for a parcel /// /// Simulator containing the parcel /// Simulator-local ID of the parcel public void RequestDwell(Simulator simulator, int localID) { ParcelDwellRequestPacket request = new ParcelDwellRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Data.LocalID = localID; request.Data.ParcelID = UUID.Zero; // Not used by clients Client.Network.SendPacket(request, simulator); } /// /// Send a request to Purchase a parcel of land /// /// The Simulator the parcel is located in /// The parcels region specific local ID /// true if this parcel is being purchased by a group /// The groups /// true to remove tier contribution if purchase is successful /// The parcels size /// The purchase price of the parcel /// public void Buy(Simulator simulator, int localID, bool forGroup, UUID groupID, bool removeContribution, int parcelArea, int parcelPrice) { ParcelBuyPacket request = new ParcelBuyPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Data.Final = true; request.Data.GroupID = groupID; request.Data.LocalID = localID; request.Data.IsGroupOwned = forGroup; request.Data.RemoveContribution = removeContribution; request.ParcelData.Area = parcelArea; request.ParcelData.Price = parcelPrice; Client.Network.SendPacket(request, simulator); } /// /// Reclaim a parcel of land /// /// The simulator the parcel is in /// The parcels region specific local ID public void Reclaim(Simulator simulator, int localID) { ParcelReclaimPacket request = new ParcelReclaimPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Data.LocalID = localID; Client.Network.SendPacket(request, simulator); } /// /// Deed a parcel to a group /// /// The simulator the parcel is in /// The parcels region specific local ID /// The groups public void DeedToGroup(Simulator simulator, int localID, UUID groupID) { ParcelDeedToGroupPacket request = new ParcelDeedToGroupPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.Data.LocalID = localID; request.Data.GroupID = groupID; Client.Network.SendPacket(request, simulator); } /// /// Request prim owners of a parcel of land. /// /// Simulator parcel is in /// The parcels region specific local ID public void RequestObjectOwners(Simulator simulator, int localID) { ParcelObjectOwnersRequestPacket request = new ParcelObjectOwnersRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ParcelData.LocalID = localID; Client.Network.SendPacket(request, simulator); } /// /// Return objects from a parcel /// /// Simulator parcel is in /// The parcels region specific local ID /// the type of objects to return, /// A list containing object owners s to return public void ReturnObjects(Simulator simulator, int localID, ObjectReturnType type, List ownerIDs) { ParcelReturnObjectsPacket request = new ParcelReturnObjectsPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.ParcelData.LocalID = localID; request.ParcelData.ReturnType = (uint)type; // A single null TaskID is (not) used for parcel object returns request.TaskIDs = new ParcelReturnObjectsPacket.TaskIDsBlock[1]; request.TaskIDs[0] = new ParcelReturnObjectsPacket.TaskIDsBlock(); request.TaskIDs[0].TaskID = UUID.Zero; // Convert the list of owner UUIDs to packet blocks if a list is given if (ownerIDs != null) { request.OwnerIDs = new ParcelReturnObjectsPacket.OwnerIDsBlock[ownerIDs.Count]; for (int i = 0; i < ownerIDs.Count; i++) { request.OwnerIDs[i] = new ParcelReturnObjectsPacket.OwnerIDsBlock(); request.OwnerIDs[i].OwnerID = ownerIDs[i]; } } else { request.OwnerIDs = new ParcelReturnObjectsPacket.OwnerIDsBlock[0]; } Client.Network.SendPacket(request, simulator); } /// /// Subdivide (split) a parcel /// /// /// /// /// /// public void ParcelSubdivide(Simulator simulator, float west, float south, float east, float north) { ParcelDividePacket divide = new ParcelDividePacket(); divide.AgentData.AgentID = Client.Self.AgentID; divide.AgentData.SessionID = Client.Self.SessionID; divide.ParcelData.East = east; divide.ParcelData.North = north; divide.ParcelData.South = south; divide.ParcelData.West = west; Client.Network.SendPacket(divide, simulator); } /// /// Join two parcels of land creating a single parcel /// /// /// /// /// /// public void ParcelJoin(Simulator simulator, float west, float south, float east, float north) { ParcelJoinPacket join = new ParcelJoinPacket(); join.AgentData.AgentID = Client.Self.AgentID; join.AgentData.SessionID = Client.Self.SessionID; join.ParcelData.East = east; join.ParcelData.North = north; join.ParcelData.South = south; join.ParcelData.West = west; Client.Network.SendPacket(join, simulator); } /// /// Get a parcels LocalID /// /// Simulator parcel is in /// Vector3 position in simulator (Z not used) /// 0 on failure, or parcel LocalID on success. /// A call to Parcels.RequestAllSimParcels is required to populate map and /// dictionary. public int GetParcelLocalID(Simulator simulator, Vector3 position) { if (simulator.ParcelMap[(byte)position.Y / 4, (byte)position.X / 4] > 0) { return simulator.ParcelMap[(byte)position.Y / 4, (byte)position.X / 4]; } else { Logger.Log(String.Format("ParcelMap returned an default/invalid value for location {0}/{1} Did you use RequestAllSimParcels() to populate the dictionaries?", (byte)position.Y / 4, (byte)position.X / 4 ), Helpers.LogLevel.Warning); return 0; } } /// /// Terraform (raise, lower, etc) an area or whole parcel of land /// /// Simulator land area is in. /// LocalID of parcel, or -1 if using bounding box /// From Enum, Raise, Lower, Level, Smooth, Etc. /// Size of area to modify /// true on successful request sent. /// Settings.STORE_LAND_PATCHES must be true, /// Parcel information must be downloaded using RequestAllSimParcels() public bool Terraform(Simulator simulator, int localID, TerraformAction action, TerraformBrushSize brushSize) { return Terraform(simulator, localID, 0f, 0f, 0f, 0f, action, brushSize, 1); } /// /// Terraform (raise, lower, etc) an area or whole parcel of land /// /// Simulator land area is in. /// west border of area to modify /// south border of area to modify /// east border of area to modify /// north border of area to modify /// From Enum, Raise, Lower, Level, Smooth, Etc. /// Size of area to modify /// true on successful request sent. /// Settings.STORE_LAND_PATCHES must be true, /// Parcel information must be downloaded using RequestAllSimParcels() public bool Terraform(Simulator simulator, float west, float south, float east, float north, TerraformAction action, TerraformBrushSize brushSize) { return Terraform(simulator, -1, west, south, east, north, action, brushSize, 1); } /// /// Terraform (raise, lower, etc) an area or whole parcel of land /// /// Simulator land area is in. /// LocalID of parcel, or -1 if using bounding box /// west border of area to modify /// south border of area to modify /// east border of area to modify /// north border of area to modify /// From Enum, Raise, Lower, Level, Smooth, Etc. /// Size of area to modify /// How many meters + or - to lower, 1 = 1 meter /// true on successful request sent. /// Settings.STORE_LAND_PATCHES must be true, /// Parcel information must be downloaded using RequestAllSimParcels() public bool Terraform(Simulator simulator, int localID, float west, float south, float east, float north, TerraformAction action, TerraformBrushSize brushSize, int seconds) { float height = 0f; int x, y; if (localID == -1) { x = (int)east - (int)west / 2; y = (int)north - (int)south / 2; } else { Parcel p; if (!simulator.Parcels.TryGetValue(localID, out p)) { Logger.Log(String.Format("Can't find parcel {0} in simulator {1}", localID, simulator), Helpers.LogLevel.Warning, Client); return false; } x = (int)p.AABBMax.X - (int)p.AABBMin.X / 2; y = (int)p.AABBMax.Y - (int)p.AABBMin.Y / 2; } if (!simulator.TerrainHeightAtPoint(x, y, out height)) { Logger.Log("Land Patch not stored for location", Helpers.LogLevel.Warning, Client); return false; } Terraform(simulator, localID, west, south, east, north, action, brushSize, seconds, height); return true; } /// /// Terraform (raise, lower, etc) an area or whole parcel of land /// /// Simulator land area is in. /// LocalID of parcel, or -1 if using bounding box /// west border of area to modify /// south border of area to modify /// east border of area to modify /// north border of area to modify /// From Enum, Raise, Lower, Level, Smooth, Etc. /// Size of area to modify /// How many meters + or - to lower, 1 = 1 meter /// Height at which the terraform operation is acting at public void Terraform(Simulator simulator, int localID, float west, float south, float east, float north, TerraformAction action, TerraformBrushSize brushSize, int seconds, float height) { ModifyLandPacket land = new ModifyLandPacket(); land.AgentData.AgentID = Client.Self.AgentID; land.AgentData.SessionID = Client.Self.SessionID; land.ModifyBlock.Action = (byte)action; land.ModifyBlock.BrushSize = (byte)brushSize; land.ModifyBlock.Seconds = seconds; land.ModifyBlock.Height = height; land.ParcelData = new ModifyLandPacket.ParcelDataBlock[1]; land.ParcelData[0] = new ModifyLandPacket.ParcelDataBlock(); land.ParcelData[0].LocalID = localID; land.ParcelData[0].West = west; land.ParcelData[0].South = south; land.ParcelData[0].East = east; land.ParcelData[0].North = north; land.ModifyBlockExtended = new ModifyLandPacket.ModifyBlockExtendedBlock[1]; land.ModifyBlockExtended[0] = new ModifyLandPacket.ModifyBlockExtendedBlock(); land.ModifyBlockExtended[0].BrushSize = (float)brushSize; Client.Network.SendPacket(land, simulator); } /// /// Sends a request to the simulator to return a list of objects owned by specific owners /// /// Simulator local ID of parcel /// Owners, Others, Etc /// List containing keys of avatars objects to select; /// if List is null will return Objects of type selectType /// Response data is returned in the event public void RequestSelectObjects(int localID, ObjectReturnType selectType, UUID ownerID) { ParcelSelectObjectsPacket select = new ParcelSelectObjectsPacket(); select.AgentData.AgentID = Client.Self.AgentID; select.AgentData.SessionID = Client.Self.SessionID; select.ParcelData.LocalID = localID; select.ParcelData.ReturnType = (uint)selectType; select.ReturnIDs = new ParcelSelectObjectsPacket.ReturnIDsBlock[1]; select.ReturnIDs[0] = new ParcelSelectObjectsPacket.ReturnIDsBlock(); select.ReturnIDs[0].ReturnID = ownerID; Client.Network.SendPacket(select); } /// /// Eject and optionally ban a user from a parcel /// /// target key of avatar to eject /// true to also ban target public void EjectUser(UUID targetID, bool ban) { EjectUserPacket eject = new EjectUserPacket(); eject.AgentData.AgentID = Client.Self.AgentID; eject.AgentData.SessionID = Client.Self.SessionID; eject.Data.TargetID = targetID; if (ban) eject.Data.Flags = 1; else eject.Data.Flags = 0; Client.Network.SendPacket(eject); } /// /// Freeze or unfreeze an avatar over your land /// /// target key to freeze /// true to freeze, false to unfreeze public void FreezeUser(UUID targetID, bool freeze) { FreezeUserPacket frz = new FreezeUserPacket(); frz.AgentData.AgentID = Client.Self.AgentID; frz.AgentData.SessionID = Client.Self.SessionID; frz.Data.TargetID = targetID; if (freeze) frz.Data.Flags = 0; else frz.Data.Flags = 1; Client.Network.SendPacket(frz); } /// /// Abandon a parcel of land /// /// Simulator parcel is in /// Simulator local ID of parcel public void ReleaseParcel(Simulator simulator, int localID) { ParcelReleasePacket abandon = new ParcelReleasePacket(); abandon.AgentData.AgentID = Client.Self.AgentID; abandon.AgentData.SessionID = Client.Self.SessionID; abandon.Data.LocalID = localID; Client.Network.SendPacket(abandon, simulator); } /// /// Requests the UUID of the parcel in a remote region at a specified location /// /// Location of the parcel in the remote region /// Remote region handle /// Remote region UUID /// If successful UUID of the remote parcel, UUID.Zero otherwise public UUID RequestRemoteParcelID(Vector3 location, ulong regionHandle, UUID regionID) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) return UUID.Zero; Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("RemoteParcelRequest"); if (url != null) { RemoteParcelRequestRequest msg = new RemoteParcelRequestRequest(); msg.Location = location; msg.RegionHandle = regionHandle; msg.RegionID = regionID; try { CapsClient request = new CapsClient(url); OSD result = request.GetResponse(msg.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); RemoteParcelRequestReply response = new RemoteParcelRequestReply(); response.Deserialize((OSDMap)result); return response.ParcelID; } catch (Exception) { Logger.Log("Failed to fetch remote parcel ID", Helpers.LogLevel.Debug, Client); } } return UUID.Zero; } /// /// Retrieves information on resources used by the parcel /// /// UUID of the parcel /// Should per object resource usage be requested /// Callback invoked when the request is complete public void GetParcelResouces(UUID parcelID, bool getDetails, LandResourcesCallback callback) { try { Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("LandResources"); CapsClient request = new CapsClient(url); request.OnComplete += delegate(CapsClient client, OSD result, Exception error) { try { if (result == null || error != null) { callback(false, null); } LandResourcesMessage response = new LandResourcesMessage(); response.Deserialize((OSDMap)result); CapsClient summaryRequest = new CapsClient(response.ScriptResourceSummary); OSD summaryResponse = summaryRequest.GetResponse(Client.Settings.CAPS_TIMEOUT); LandResourcesInfo res = new LandResourcesInfo(); res.Deserialize((OSDMap)summaryResponse); if (response.ScriptResourceDetails != null && getDetails) { CapsClient detailRequest = new CapsClient(response.ScriptResourceDetails); OSD detailResponse = detailRequest.GetResponse(Client.Settings.CAPS_TIMEOUT); res.Deserialize((OSDMap)detailResponse); } callback(true, res); } catch (Exception ex) { Logger.Log("Failed fetching land resources", Helpers.LogLevel.Error, Client, ex); callback(false, null); } }; LandResourcesRequest param = new LandResourcesRequest(); param.ParcelID = parcelID; request.BeginGetResponse(param.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } catch (Exception ex) { Logger.Log("Failed fetching land resources:", Helpers.LogLevel.Error, Client, ex); callback(false, null); } } #endregion Public Methods #region Packet Handlers /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// Raises the event protected void ParcelDwellReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DwellReply != null || Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL == true) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ParcelDwellReplyPacket dwell = (ParcelDwellReplyPacket)packet; lock (simulator.Parcels.Dictionary) { if (simulator.Parcels.Dictionary.ContainsKey(dwell.Data.LocalID)) { Parcel parcel = simulator.Parcels.Dictionary[dwell.Data.LocalID]; parcel.Dwell = dwell.Data.Dwell; simulator.Parcels.Dictionary[dwell.Data.LocalID] = parcel; } } if (m_DwellReply != null) { OnParcelDwellReply(new ParcelDwellReplyEventArgs(dwell.Data.ParcelID, dwell.Data.LocalID, dwell.Data.Dwell)); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// Raises the event protected void ParcelInfoReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_ParcelInfo != null) { Packet packet = e.Packet; ParcelInfoReplyPacket info = (ParcelInfoReplyPacket)packet; ParcelInfo parcelInfo = new ParcelInfo(); parcelInfo.ActualArea = info.Data.ActualArea; parcelInfo.AuctionID = info.Data.AuctionID; parcelInfo.BillableArea = info.Data.BillableArea; parcelInfo.Description = Utils.BytesToString(info.Data.Desc); parcelInfo.Dwell = info.Data.Dwell; parcelInfo.GlobalX = info.Data.GlobalX; parcelInfo.GlobalY = info.Data.GlobalY; parcelInfo.GlobalZ = info.Data.GlobalZ; parcelInfo.ID = info.Data.ParcelID; parcelInfo.Mature = ((info.Data.Flags & 1) != 0) ? true : false; parcelInfo.Name = Utils.BytesToString(info.Data.Name); parcelInfo.OwnerID = info.Data.OwnerID; parcelInfo.SalePrice = info.Data.SalePrice; parcelInfo.SimName = Utils.BytesToString(info.Data.SimName); parcelInfo.SnapshotID = info.Data.SnapshotID; OnParcelInfoReply(new ParcelInfoReplyEventArgs(parcelInfo)); } } protected void ParcelPropertiesReplyHandler(string capsKey, IMessage message, Simulator simulator) { if (m_ParcelProperties != null || Client.Settings.PARCEL_TRACKING == true) { ParcelPropertiesMessage msg = (ParcelPropertiesMessage)message; Parcel parcel = new Parcel(msg.LocalID); parcel.AABBMax = msg.AABBMax; parcel.AABBMin = msg.AABBMin; parcel.Area = msg.Area; parcel.AuctionID = msg.AuctionID; parcel.AuthBuyerID = msg.AuthBuyerID; parcel.Bitmap = msg.Bitmap; parcel.Category = msg.Category; parcel.ClaimDate = msg.ClaimDate; parcel.ClaimPrice = msg.ClaimPrice; parcel.Desc = msg.Desc; parcel.Flags = msg.ParcelFlags; parcel.GroupID = msg.GroupID; parcel.GroupPrims = msg.GroupPrims; parcel.IsGroupOwned = msg.IsGroupOwned; parcel.Landing = msg.LandingType; parcel.MaxPrims = msg.MaxPrims; parcel.Media.MediaAutoScale = msg.MediaAutoScale; parcel.Media.MediaID = msg.MediaID; parcel.Media.MediaURL = msg.MediaURL; parcel.MusicURL = msg.MusicURL; parcel.Name = msg.Name; parcel.OtherCleanTime = msg.OtherCleanTime; parcel.OtherCount = msg.OtherCount; parcel.OtherPrims = msg.OtherPrims; parcel.OwnerID = msg.OwnerID; parcel.OwnerPrims = msg.OwnerPrims; parcel.ParcelPrimBonus = msg.ParcelPrimBonus; parcel.PassHours = msg.PassHours; parcel.PassPrice = msg.PassPrice; parcel.PublicCount = msg.PublicCount; parcel.RegionDenyAgeUnverified = msg.RegionDenyAgeUnverified; parcel.RegionDenyAnonymous = msg.RegionDenyAnonymous; parcel.RegionPushOverride = msg.RegionPushOverride; parcel.RentPrice = msg.RentPrice; ParcelResult result = msg.RequestResult; parcel.SalePrice = msg.SalePrice; int selectedPrims = msg.SelectedPrims; parcel.SelfCount = msg.SelfCount; int sequenceID = msg.SequenceID; parcel.SimWideMaxPrims = msg.SimWideMaxPrims; parcel.SimWideTotalPrims = msg.SimWideTotalPrims; bool snapSelection = msg.SnapSelection; parcel.SnapshotID = msg.SnapshotID; parcel.Status = msg.Status; parcel.TotalPrims = msg.TotalPrims; parcel.UserLocation = msg.UserLocation; parcel.UserLookAt = msg.UserLookAt; parcel.Media.MediaDesc = msg.MediaDesc; parcel.Media.MediaHeight = msg.MediaHeight; parcel.Media.MediaWidth = msg.MediaWidth; parcel.Media.MediaLoop = msg.MediaLoop; parcel.Media.MediaType = msg.MediaType; parcel.ObscureMedia = msg.ObscureMedia; parcel.ObscureMusic = msg.ObscureMusic; parcel.SeeAVs = msg.SeeAVs; parcel.AnyAVSounds = msg.AnyAVSounds; parcel.GroupAVSounds = msg.GroupAVSounds; if (Client.Settings.PARCEL_TRACKING) { lock (simulator.Parcels.Dictionary) simulator.Parcels.Dictionary[parcel.LocalID] = parcel; bool set = false; int y, x, index, bit; for (y = 0; y < 64; y++) { for (x = 0; x < 64; x++) { index = (y * 64) + x; bit = index % 8; index >>= 3; if ((parcel.Bitmap[index] & (1 << bit)) != 0) { simulator.ParcelMap[y, x] = parcel.LocalID; set = true; } } } if (!set) { Logger.Log("Received a parcel with a bitmap that did not map to any locations", Helpers.LogLevel.Warning); } } if (sequenceID.Equals(int.MaxValue) && WaitForSimParcel != null) WaitForSimParcel.Set(); // auto request acl, will be stored in parcel tracking dictionary if enabled if (Client.Settings.ALWAYS_REQUEST_PARCEL_ACL) Client.Parcels.RequestParcelAccessList(simulator, parcel.LocalID, AccessList.Both, sequenceID); // auto request dwell, will be stored in parcel tracking dictionary if enables if (Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL) Client.Parcels.RequestDwell(simulator, parcel.LocalID); // Fire the callback for parcel properties being received if (m_ParcelProperties != null) { OnParcelProperties(new ParcelPropertiesEventArgs(simulator, parcel, result, selectedPrims, sequenceID, snapSelection)); } // Check if all of the simulator parcels have been retrieved, if so fire another callback if (simulator.IsParcelMapFull() && m_SimParcelsDownloaded != null) { OnSimParcelsDownloaded(new SimParcelsDownloadedEventArgs(simulator, simulator.Parcels, simulator.ParcelMap)); } } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// Raises the event protected void ParcelAccessListReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_ParcelACL != null || Client.Settings.ALWAYS_REQUEST_PARCEL_ACL == true) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ParcelAccessListReplyPacket reply = (ParcelAccessListReplyPacket)packet; List accessList = new List(reply.List.Length); for (int i = 0; i < reply.List.Length; i++) { ParcelAccessEntry pae = new ParcelAccessEntry(); pae.AgentID = reply.List[i].ID; pae.Time = Utils.UnixTimeToDateTime((uint)reply.List[i].Time); pae.Flags = (AccessList)reply.List[i].Flags; accessList.Add(pae); } lock (simulator.Parcels.Dictionary) { if (simulator.Parcels.Dictionary.ContainsKey(reply.Data.LocalID)) { Parcel parcel = simulator.Parcels.Dictionary[reply.Data.LocalID]; if ((AccessList)reply.Data.Flags == AccessList.Ban) parcel.AccessBlackList = accessList; else parcel.AccessWhiteList = accessList; simulator.Parcels.Dictionary[reply.Data.LocalID] = parcel; } } if (m_ParcelACL != null) { OnParcelAccessListReply(new ParcelAccessListReplyEventArgs(simulator, reply.Data.SequenceID, reply.Data.LocalID, reply.Data.Flags, accessList)); } } } protected void ParcelObjectOwnersReplyHandler(string capsKey, IMessage message, Simulator simulator) { if (m_ParcelObjectOwnersReply != null) { List primOwners = new List(); ParcelObjectOwnersReplyMessage msg = (ParcelObjectOwnersReplyMessage)message; for (int i = 0; i < msg.PrimOwnersBlock.Length; i++) { ParcelPrimOwners primOwner = new ParcelPrimOwners(); primOwner.OwnerID = msg.PrimOwnersBlock[i].OwnerID; primOwner.Count = msg.PrimOwnersBlock[i].Count; primOwner.IsGroupOwned = msg.PrimOwnersBlock[i].IsGroupOwned; primOwner.OnlineStatus = msg.PrimOwnersBlock[i].OnlineStatus; primOwner.NewestPrim = msg.PrimOwnersBlock[i].TimeStamp; primOwners.Add(primOwner); } OnParcelObjectOwnersReply(new ParcelObjectOwnersReplyEventArgs(simulator, primOwners)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// Raises the event protected void SelectParcelObjectsReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_ForceSelectObjects != null) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ForceObjectSelectPacket reply = (ForceObjectSelectPacket)packet; List objectIDs = new List(reply.Data.Length); for (int i = 0; i < reply.Data.Length; i++) { objectIDs.Add(reply.Data[i].LocalID); } OnForceSelectObjectsReply(new ForceSelectObjectsReplyEventArgs(simulator, objectIDs, reply._Header.ResetList)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// Raises the event protected void ParcelMediaUpdateHandler(object sender, PacketReceivedEventArgs e) { if (m_ParcelMediaUpdateReply != null) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ParcelMediaUpdatePacket reply = (ParcelMediaUpdatePacket)packet; ParcelMedia media = new ParcelMedia(); media.MediaAutoScale = (reply.DataBlock.MediaAutoScale == (byte)0x1) ? true : false; media.MediaID = reply.DataBlock.MediaID; media.MediaDesc = Utils.BytesToString(reply.DataBlockExtended.MediaDesc); media.MediaHeight = reply.DataBlockExtended.MediaHeight; media.MediaLoop = ((reply.DataBlockExtended.MediaLoop & 1) != 0) ? true : false; media.MediaType = Utils.BytesToString(reply.DataBlockExtended.MediaType); media.MediaWidth = reply.DataBlockExtended.MediaWidth; media.MediaURL = Utils.BytesToString(reply.DataBlock.MediaURL); OnParcelMediaUpdateReply(new ParcelMediaUpdateReplyEventArgs(simulator, media)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void ParcelOverlayHandler(object sender, PacketReceivedEventArgs e) { const int OVERLAY_COUNT = 4; Packet packet = e.Packet; Simulator simulator = e.Simulator; ParcelOverlayPacket overlay = (ParcelOverlayPacket)packet; if (overlay.ParcelData.SequenceID >= 0 && overlay.ParcelData.SequenceID < OVERLAY_COUNT) { int length = overlay.ParcelData.Data.Length; Buffer.BlockCopy(overlay.ParcelData.Data, 0, simulator.ParcelOverlay, overlay.ParcelData.SequenceID * length, length); simulator.ParcelOverlaysReceived++; if (simulator.ParcelOverlaysReceived >= OVERLAY_COUNT) { // TODO: ParcelOverlaysReceived should become internal, and reset to zero every // time it hits four. Also need a callback here } } else { Logger.Log("Parcel overlay with sequence ID of " + overlay.ParcelData.SequenceID + " received from " + simulator.ToString(), Helpers.LogLevel.Warning, Client); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data /// Raises the event protected void ParcelMediaCommandMessagePacketHandler(object sender, PacketReceivedEventArgs e) { if (m_ParcelMediaCommand != null) { Packet packet = e.Packet; Simulator simulator = e.Simulator; ParcelMediaCommandMessagePacket pmc = (ParcelMediaCommandMessagePacket)packet; ParcelMediaCommandMessagePacket.CommandBlockBlock block = pmc.CommandBlock; OnParcelMediaCommand(new ParcelMediaCommandEventArgs(simulator, pmc.Header.Sequence, (ParcelFlags)block.Flags, (ParcelMediaCommand)block.Command, block.Time)); } } #endregion Packet Handlers } #region EventArgs classes /// Contains a parcels dwell data returned from the simulator in response to an public class ParcelDwellReplyEventArgs : EventArgs { private readonly UUID m_ParcelID; private readonly int m_LocalID; private readonly float m_Dwell; /// Get the global ID of the parcel public UUID ParcelID { get { return m_ParcelID; } } /// Get the simulator specific ID of the parcel public int LocalID { get { return m_LocalID; } } /// Get the calculated dwell public float Dwell { get { return m_Dwell; } } /// /// Construct a new instance of the ParcelDwellReplyEventArgs class /// /// The global ID of the parcel /// The simulator specific ID of the parcel /// The calculated dwell for the parcel public ParcelDwellReplyEventArgs(UUID parcelID, int localID, float dwell) { this.m_ParcelID = parcelID; this.m_LocalID = localID; this.m_Dwell = dwell; } } /// Contains basic parcel information data returned from the /// simulator in response to an request public class ParcelInfoReplyEventArgs : EventArgs { private readonly ParcelInfo m_Parcel; /// Get the object containing basic parcel info public ParcelInfo Parcel { get { return m_Parcel; } } /// /// Construct a new instance of the ParcelInfoReplyEventArgs class /// /// The object containing basic parcel info public ParcelInfoReplyEventArgs(ParcelInfo parcel) { this.m_Parcel = parcel; } } /// Contains basic parcel information data returned from the simulator in response to an request public class ParcelPropertiesEventArgs : EventArgs { private readonly Simulator m_Simulator; private Parcel m_Parcel; private readonly ParcelResult m_Result; private readonly int m_SelectedPrims; private readonly int m_SequenceID; private readonly bool m_SnapSelection; /// Get the simulator the parcel is located in public Simulator Simulator { get { return m_Simulator; } } /// Get the object containing the details /// If Result is NoData, this object will not contain valid data public Parcel Parcel { get { return m_Parcel; } } /// Get the result of the request public ParcelResult Result { get { return m_Result; } } /// Get the number of primitieves your agent is /// currently selecting and or sitting on in this parcel public int SelectedPrims { get { return m_SelectedPrims; } } /// Get the user assigned ID used to correlate a request with /// these results public int SequenceID { get { return m_SequenceID; } } /// TODO: public bool SnapSelection { get { return m_SnapSelection; } } /// /// Construct a new instance of the ParcelPropertiesEventArgs class /// /// The object containing the details /// The object containing the details /// The result of the request /// The number of primitieves your agent is /// currently selecting and or sitting on in this parcel /// The user assigned ID used to correlate a request with /// these results /// TODO: public ParcelPropertiesEventArgs(Simulator simulator, Parcel parcel, ParcelResult result, int selectedPrims, int sequenceID, bool snapSelection) { this.m_Simulator = simulator; this.m_Parcel = parcel; this.m_Result = result; this.m_SelectedPrims = selectedPrims; this.m_SequenceID = sequenceID; this.m_SnapSelection = snapSelection; } } /// Contains blacklist and whitelist data returned from the simulator in response to an request public class ParcelAccessListReplyEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly int m_SequenceID; private readonly int m_LocalID; private readonly uint m_Flags; private readonly List m_AccessList; /// Get the simulator the parcel is located in public Simulator Simulator { get { return m_Simulator; } } /// Get the user assigned ID used to correlate a request with /// these results public int SequenceID { get { return m_SequenceID; } } /// Get the simulator specific ID of the parcel public int LocalID { get { return m_LocalID; } } /// TODO: public uint Flags { get { return m_Flags; } } /// Get the list containing the white/blacklisted agents for the parcel public List AccessList { get { return m_AccessList; } } /// /// Construct a new instance of the ParcelAccessListReplyEventArgs class /// /// The simulator the parcel is located in /// The user assigned ID used to correlate a request with /// these results /// The simulator specific ID of the parcel /// TODO: /// The list containing the white/blacklisted agents for the parcel public ParcelAccessListReplyEventArgs(Simulator simulator, int sequenceID, int localID, uint flags, List accessEntries) { this.m_Simulator = simulator; this.m_SequenceID = sequenceID; this.m_LocalID = localID; this.m_Flags = flags; this.m_AccessList = accessEntries; } } /// Contains blacklist and whitelist data returned from the /// simulator in response to an request public class ParcelObjectOwnersReplyEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly List m_Owners; /// Get the simulator the parcel is located in public Simulator Simulator { get { return m_Simulator; } } /// Get the list containing prim ownership counts public List PrimOwners { get { return m_Owners; } } /// /// Construct a new instance of the ParcelObjectOwnersReplyEventArgs class /// /// The simulator the parcel is located in /// The list containing prim ownership counts public ParcelObjectOwnersReplyEventArgs(Simulator simulator, List primOwners) { this.m_Simulator = simulator; this.m_Owners = primOwners; } } /// Contains the data returned when all parcel data has been retrieved from a simulator public class SimParcelsDownloadedEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly InternalDictionary m_Parcels; private readonly int[,] m_ParcelMap; /// Get the simulator the parcel data was retrieved from public Simulator Simulator { get { return m_Simulator; } } /// A dictionary containing the parcel data where the key correlates to the ParcelMap entry public InternalDictionary Parcels { get { return m_Parcels; } } /// Get the multidimensional array containing a x,y grid mapped /// to each 64x64 parcel's LocalID. public int[,] ParcelMap { get { return m_ParcelMap; } } /// /// Construct a new instance of the SimParcelsDownloadedEventArgs class /// /// The simulator the parcel data was retrieved from /// The dictionary containing the parcel data /// The multidimensional array containing a x,y grid mapped /// to each 64x64 parcel's LocalID. public SimParcelsDownloadedEventArgs(Simulator simulator, InternalDictionary simParcels, int[,] parcelMap) { this.m_Simulator = simulator; this.m_Parcels = simParcels; this.m_ParcelMap = parcelMap; } } /// Contains the data returned when a request public class ForceSelectObjectsReplyEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly List m_ObjectIDs; private readonly bool m_ResetList; /// Get the simulator the parcel data was retrieved from public Simulator Simulator { get { return m_Simulator; } } /// Get the list of primitive IDs public List ObjectIDs { get { return m_ObjectIDs; } } /// true if the list is clean and contains the information /// only for a given request public bool ResetList { get { return m_ResetList; } } /// /// Construct a new instance of the ForceSelectObjectsReplyEventArgs class /// /// The simulator the parcel data was retrieved from /// The list of primitive IDs /// true if the list is clean and contains the information /// only for a given request public ForceSelectObjectsReplyEventArgs(Simulator simulator, List objectIDs, bool resetList) { this.m_Simulator = simulator; this.m_ObjectIDs = objectIDs; this.m_ResetList = resetList; } } /// Contains data when the media data for a parcel the avatar is on changes public class ParcelMediaUpdateReplyEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly ParcelMedia m_ParcelMedia; /// Get the simulator the parcel media data was updated in public Simulator Simulator { get { return m_Simulator; } } /// Get the updated media information public ParcelMedia Media { get { return m_ParcelMedia; } } /// /// Construct a new instance of the ParcelMediaUpdateReplyEventArgs class /// /// the simulator the parcel media data was updated in /// The updated media information public ParcelMediaUpdateReplyEventArgs(Simulator simulator, ParcelMedia media) { this.m_Simulator = simulator; this.m_ParcelMedia = media; } } /// Contains the media command for a parcel the agent is currently on public class ParcelMediaCommandEventArgs : EventArgs { private readonly Simulator m_Simulator; private readonly uint m_Sequence; private readonly ParcelFlags m_ParcelFlags; private readonly ParcelMediaCommand m_MediaCommand; private readonly float m_Time; /// Get the simulator the parcel media command was issued in public Simulator Simulator { get { return m_Simulator; } } /// public uint Sequence { get { return m_Sequence; } } /// public ParcelFlags ParcelFlags { get { return m_ParcelFlags; } } /// Get the media command that was sent public ParcelMediaCommand MediaCommand { get { return m_MediaCommand; } } /// public float Time { get { return m_Time; } } /// /// Construct a new instance of the ParcelMediaCommandEventArgs class /// /// The simulator the parcel media command was issued in /// /// /// The media command that was sent /// public ParcelMediaCommandEventArgs(Simulator simulator, uint sequence, ParcelFlags flags, ParcelMediaCommand command, float time) { this.m_Simulator = simulator; this.m_Sequence = sequence; this.m_ParcelFlags = flags; this.m_MediaCommand = command; this.m_Time = time; } } #endregion }