/* * Copyright (c) 2006-2016, openmetaverse.co * 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 System.Collections.Generic; using System.Linq; using OpenMetaverse.Packets; using OpenMetaverse.Interfaces; using OpenMetaverse.Messages.Linden; namespace OpenMetaverse { /// /// Access to the data server which allows searching for land, events, people, etc /// public class DirectoryManager { #region Enums /// Classified Ad categories public enum ClassifiedCategories { /// Classified is listed in the Any category Any = 0, /// Classified is shopping related Shopping, /// Classified is LandRental, /// PropertyRental, /// SpecialAttraction, /// NewProducts, /// Employment, /// Wanted, /// Service, /// Personal } /// Event Categories public enum EventCategories { /// All = 0, /// Discussion = 18, /// Sports = 19, /// LiveMusic = 20, /// Commercial = 22, /// Nightlife = 23, /// Games = 24, /// Pageants = 25, /// Education = 26, /// Arts = 27, /// Charity = 28, /// Miscellaneous = 29 } /// /// Query Flags used in many of the DirectoryManager methods to specify which query to execute and how to return the results. /// /// Flags can be combined using the | (pipe) character, not all flags are available in all queries /// [Flags] public enum DirFindFlags { /// Query the People database People = 1 << 0, /// Online = 1 << 1, // //[Obsolete] //Places = 1 << 2, /// Events = 1 << 3, /// Query the Groups database Groups = 1 << 4, /// Query the Events database DateEvents = 1 << 5, /// Query the land holdings database for land owned by the currently connected agent AgentOwned = 1 << 6, /// ForSale = 1 << 7, /// Query the land holdings database for land which is owned by a Group GroupOwned = 1 << 8, // //[Obsolete] //Auction = 1 << 9, /// Specifies the query should pre sort the results based upon traffic /// when searching the Places database DwellSort = 1 << 10, /// PgSimsOnly = 1 << 11, /// PicturesOnly = 1 << 12, /// PgEventsOnly = 1 << 13, /// MatureSimsOnly = 1 << 14, /// Specifies the query should pre sort the results in an ascending order when searching the land sales database. /// This flag is only used when searching the land sales database SortAsc = 1 << 15, /// Specifies the query should pre sort the results using the SalePrice field when searching the land sales database. /// This flag is only used when searching the land sales database PricesSort = 1 << 16, /// Specifies the query should pre sort the results by calculating the average price/sq.m (SalePrice / Area) when searching the land sales database. /// This flag is only used when searching the land sales database PerMeterSort = 1 << 17, /// Specifies the query should pre sort the results using the ParcelSize field when searching the land sales database. /// This flag is only used when searching the land sales database AreaSort = 1 << 18, /// Specifies the query should pre sort the results using the Name field when searching the land sales database. /// This flag is only used when searching the land sales database NameSort = 1 << 19, /// When set, only parcels less than the specified Price will be included when searching the land sales database. /// This flag is only used when searching the land sales database LimitByPrice = 1 << 20, /// When set, only parcels greater than the specified Size will be included when searching the land sales database. /// This flag is only used when searching the land sales database LimitByArea = 1 << 21, /// FilterMature = 1 << 22, /// PGOnly = 1 << 23, /// Include PG land in results. This flag is used when searching both the Groups, Events and Land sales databases IncludePG = 1 << 24, /// Include Mature land in results. This flag is used when searching both the Groups, Events and Land sales databases IncludeMature = 1 << 25, /// Include Adult land in results. This flag is used when searching both the Groups, Events and Land sales databases IncludeAdult = 1 << 26, /// AdultOnly = 1 << 27 } /// /// Land types to search dataserver for /// [Flags] public enum SearchTypeFlags { /// Search Auction, Mainland and Estate Any = -1, /// Land which is currently up for auction Auction = 1 << 1, // Land available to new landowners (formerly the FirstLand program) //[Obsolete] //Newbie = 1 << 2, /// Parcels which are on the mainland (Linden owned) continents Mainland = 1 << 3, /// Parcels which are on privately owned simulators Estate = 1 << 4 } /// /// The content rating of the event /// public enum EventFlags { /// Event is PG PG = 0, /// Event is Mature Mature = 1, /// Event is Adult Adult = 2 } /// /// Classified Ad Options /// /// There appear to be two formats the flags are packed in. /// This set of flags is for the newer style [Flags] public enum ClassifiedFlags : byte { /// None = 1 << 0, /// Mature = 1 << 1, /// Enabled = 1 << 2, // HasPrice = 1 << 3, // Deprecated /// UpdateTime = 1 << 4, /// AutoRenew = 1 << 5 } /// /// Classified ad query options /// [Flags] public enum ClassifiedQueryFlags { /// Include all ads in results All = PG | Mature | Adult, /// Include PG ads in results PG = 1 << 2, /// Include Mature ads in results Mature = 1 << 3, /// Include Adult ads in results Adult = 1 << 6, } /// /// The For Sale flag in PlacesReplyData /// public enum PlacesFlags : byte { /// Parcel is not listed for sale NotForSale = 0, /// Parcel is For Sale ForSale = 128 } #endregion #region Structs /// /// A classified ad on the grid /// public struct Classified { /// UUID for this ad, useful for looking up detailed /// information about it public UUID ID; /// The title of this classified ad public string Name; /// Flags that show certain options applied to the classified public ClassifiedFlags Flags; /// Creation date of the ad public DateTime CreationDate; /// Expiration date of the ad public DateTime ExpirationDate; /// Price that was paid for this ad public int Price; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// A parcel retrieved from the dataserver such as results from the /// "For-Sale" listings or "Places" Search /// public struct DirectoryParcel { /// The unique dataserver parcel ID /// This id is used to obtain additional information from the entry /// by using the method public UUID ID; /// A string containing the name of the parcel public string Name; /// The size of the parcel /// This field is not returned for Places searches public int ActualArea; /// The price of the parcel /// This field is not returned for Places searches public int SalePrice; /// If True, this parcel is flagged to be auctioned public bool Auction; /// If true, this parcel is currently set for sale public bool ForSale; /// Parcel traffic public float Dwell; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// An Avatar returned from the dataserver /// public struct AgentSearchData { /// Online status of agent /// This field appears to be obsolete and always returns false public bool Online; /// The agents first name public string FirstName; /// The agents last name public string LastName; /// The agents public UUID AgentID; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// Response to a "Groups" Search /// public struct GroupSearchData { /// The Group ID public UUID GroupID; /// The name of the group public string GroupName; /// The current number of members public int Members; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// Parcel information returned from a request /// /// Represents one of the following: /// A parcel of land on the grid that has its Show In Search flag set /// A parcel of land owned by the agent making the request /// A parcel of land owned by a group the agent making the request is a member of /// /// /// In a request for Group Land, the First record will contain an empty record /// /// Note: This is not the same as searching the land for sale data source /// public struct PlacesSearchData { /// The ID of the Agent of Group that owns the parcel public UUID OwnerID; /// The name public string Name; /// The description public string Desc; /// The Size of the parcel public int ActualArea; /// The billable Size of the parcel, for mainland /// parcels this will match the ActualArea field. For Group owned land this will be 10 percent smaller /// than the ActualArea. For Estate land this will always be 0 public int BillableArea; /// Indicates the ForSale status of the parcel public PlacesFlags Flags; /// The Gridwide X position public float GlobalX; /// The Gridwide Y position public float GlobalY; /// The Z position of the parcel, or 0 if no landing point set public float GlobalZ; /// The name of the Region the parcel is located in public string SimName; /// The Asset ID of the parcels Snapshot texture public UUID SnapshotID; /// The calculated visitor traffic public float Dwell; /// The billing product SKU /// Known values are: /// /// 023Mainland / Full Region /// 024Estate / Full Region /// 027Estate / Openspace /// 029Estate / Homestead /// 129Mainland / Homestead (Linden Owned) /// /// public string SKU; /// No longer used, will always be 0 public int Price; /// Get a SL URL for the parcel /// A string, containing a standard SLURL public string ToSLurl() { float x, y; Helpers.GlobalPosToRegionHandle(this.GlobalX, this.GlobalY, out x, out y); return "secondlife://" + this.SimName + "/" + x + "/" + y + "/" + this.GlobalZ; } /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// An "Event" Listing summary /// public struct EventsSearchData { /// The ID of the event creator public UUID Owner; /// The name of the event public string Name; /// The events ID public uint ID; /// A string containing the short date/time the event will begin public string Date; /// The event start time in Unixtime (seconds since epoch) public uint Time; /// The events maturity rating public EventFlags Flags; /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } /// /// The details of an "Event" /// public struct EventInfo { /// The events ID public uint ID; /// The ID of the event creator public UUID Creator; /// The name of the event public string Name; /// The category public EventCategories Category; /// The events description public string Desc; /// The short date/time the event will begin public string Date; /// The event start time in Unixtime (seconds since epoch) UTC adjusted public uint DateUTC; /// The length of the event in minutes public uint Duration; /// 0 if no cover charge applies public uint Cover; /// The cover charge amount in L$ if applicable public uint Amount; /// The name of the region where the event is being held public string SimName; /// The gridwide location of the event public Vector3d GlobalPos; /// The maturity rating public EventFlags Flags; /// Get a SL URL for the parcel where the event is hosted /// A string, containing a standard SLURL public string ToSLurl() { float x, y; Helpers.GlobalPosToRegionHandle((float)this.GlobalPos.X, (float)this.GlobalPos.Y, out x, out y); return "secondlife://" + this.SimName + "/" + x + "/" + y + "/" + this.GlobalPos.Z; } /// Print the struct data as a string /// A string containing the field name, and field value public override string ToString() { return Helpers.StructToString(this); } } #endregion Structs #region Event delegates, Raise Events /// The event subscribers. null if no subcribers private EventHandler m_EventInfoReply; /// Raises the EventInfoReply event /// An EventInfoReplyEventArgs object containing the /// data returned from the data server protected virtual void OnEventInfo(EventInfoReplyEventArgs e) { EventHandler handler = m_EventInfoReply; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_EventDetailLock = new object(); /// Raised when the data server responds to a request. public event EventHandler EventInfoReply { add { lock (m_EventDetailLock) { m_EventInfoReply += value; } } remove { lock (m_EventDetailLock) { m_EventInfoReply -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirEvents; /// Raises the DirEventsReply event /// An DirEventsReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirEvents(DirEventsReplyEventArgs e) { EventHandler handler = m_DirEvents; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirEventsLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirEventsReply { add { lock (m_DirEventsLock) { m_DirEvents += value; } } remove { lock (m_DirEventsLock) { m_DirEvents -= value; } } } /// The event subscribers. null if no subscribers private EventHandler m_Places; /// Raises the PlacesReply event /// A PlacesReplyEventArgs object containing the /// data returned from the data server protected virtual void OnPlaces(PlacesReplyEventArgs e) { EventHandler handler = m_Places; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_PlacesLock = new object(); /// Raised when the data server responds to a request. public event EventHandler PlacesReply { add { lock (m_PlacesLock) { m_Places += value; } } remove { lock (m_PlacesLock) { m_Places -= value; } } } /// The event subscribers. null if no subscribers private EventHandler m_DirPlaces; /// Raises the DirPlacesReply event /// A DirPlacesReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirPlaces(DirPlacesReplyEventArgs e) { EventHandler handler = m_DirPlaces; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirPlacesLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirPlacesReply { add { lock (m_DirPlacesLock) { m_DirPlaces += value; } } remove { lock (m_DirPlacesLock) { m_DirPlaces -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirClassifieds; /// Raises the DirClassifiedsReply event /// A DirClassifiedsReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirClassifieds(DirClassifiedsReplyEventArgs e) { EventHandler handler = m_DirClassifieds; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirClassifiedsLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirClassifiedsReply { add { lock (m_DirClassifiedsLock) { m_DirClassifieds += value; } } remove { lock (m_DirClassifiedsLock) { m_DirClassifieds -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirGroups; /// Raises the DirGroupsReply event /// A DirGroupsReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirGroups(DirGroupsReplyEventArgs e) { EventHandler handler = m_DirGroups; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirGroupsLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirGroupsReply { add { lock (m_DirGroupsLock) { m_DirGroups += value; } } remove { lock (m_DirGroupsLock) { m_DirGroups -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirPeople; /// Raises the DirPeopleReply event /// A DirPeopleReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirPeople(DirPeopleReplyEventArgs e) { EventHandler handler = m_DirPeople; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirPeopleLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirPeopleReply { add { lock (m_DirPeopleLock) { m_DirPeople += value; } } remove { lock (m_DirPeopleLock) { m_DirPeople -= value; } } } /// The event subscribers. null if no subcribers private EventHandler m_DirLandReply; /// Raises the DirLandReply event /// A DirLandReplyEventArgs object containing the /// data returned from the data server protected virtual void OnDirLand(DirLandReplyEventArgs e) { EventHandler handler = m_DirLandReply; handler?.Invoke(this, e); } /// Thread sync lock object private readonly object m_DirLandLock = new object(); /// Raised when the data server responds to a request. public event EventHandler DirLandReply { add { lock (m_DirLandLock) { m_DirLandReply += value; } } remove { lock (m_DirLandLock) { m_DirLandReply -= value; } } } #endregion #region Private Members private GridClient Client; #endregion #region Constructors /// /// Constructs a new instance of the DirectoryManager class /// /// An instance of GridClient public DirectoryManager(GridClient client) { Client = client; Client.Network.RegisterCallback(PacketType.DirClassifiedReply, DirClassifiedReplyHandler); // Deprecated, replies come in over capabilities Client.Network.RegisterCallback(PacketType.DirLandReply, DirLandReplyHandler); Client.Network.RegisterEventCallback("DirLandReply", DirLandReplyEventHandler); Client.Network.RegisterCallback(PacketType.DirPeopleReply, DirPeopleReplyHandler); Client.Network.RegisterCallback(PacketType.DirGroupsReply, DirGroupsReplyHandler); // Deprecated as of viewer 1.2.3 Client.Network.RegisterCallback(PacketType.PlacesReply, PlacesReplyHandler); Client.Network.RegisterEventCallback("PlacesReply", PlacesReplyEventHandler); Client.Network.RegisterCallback(PacketType.DirEventsReply, EventsReplyHandler); Client.Network.RegisterCallback(PacketType.EventInfoReply, EventInfoReplyHandler); Client.Network.RegisterCallback(PacketType.DirPlacesReply, DirPlacesReplyHandler); } #endregion #region Public Methods // Obsoleted due to new Adult search option [Obsolete("Use Overload with ClassifiedQueryFlags option instead")] public UUID StartClassifiedSearch(string searchText, ClassifiedCategories category, bool mature) { return UUID.Zero; } /// /// Query the data server for a list of classified ads containing the specified string. /// Defaults to searching for classified placed in any category, and includes PG, Adult and Mature /// results. /// /// Responses are sent 16 per response packet, there is no way to know how many results a query reply will contain however assuming /// the reply packets arrived ordered, a response with less than 16 entries would indicate all results have been received /// /// The event is raised when a response is received from the simulator /// /// A string containing a list of keywords to search for /// A UUID to correlate the results when the event is raised public UUID StartClassifiedSearch(string searchText) { return StartClassifiedSearch(searchText, ClassifiedCategories.Any, ClassifiedQueryFlags.All); } /// /// Query the data server for a list of classified ads which contain specified keywords (Overload) /// /// The event is raised when a response is received from the simulator /// /// A string containing a list of keywords to search for /// The category to search /// A set of flags which can be ORed to modify query options /// such as classified maturity rating. /// A UUID to correlate the results when the event is raised /// /// Search classified ads containing the key words "foo" and "bar" in the "Any" category that are either PG or Mature /// /// UUID searchID = StartClassifiedSearch("foo bar", ClassifiedCategories.Any, ClassifiedQueryFlags.PG | ClassifiedQueryFlags.Mature); /// /// /// /// Responses are sent 16 at a time, there is no way to know how many results a query reply will contain however assuming /// the reply packets arrived ordered, a response with less than 16 entries would indicate all results have been received /// public UUID StartClassifiedSearch(string searchText, ClassifiedCategories category, ClassifiedQueryFlags queryFlags) { DirClassifiedQueryPacket query = new DirClassifiedQueryPacket(); UUID queryID = UUID.Random(); query.AgentData.AgentID = Client.Self.AgentID; query.AgentData.SessionID = Client.Self.SessionID; query.QueryData.Category = (uint)category; query.QueryData.QueryFlags = (uint)queryFlags; query.QueryData.QueryID = queryID; query.QueryData.QueryText = Utils.StringToBytes(searchText); Client.Network.SendPacket(query); return queryID; } /// /// Starts search for places (Overloaded) /// /// The event is raised when a response is received from the simulator /// /// Search text /// Each request is limited to 100 places /// being returned. To get the first 100 result entries of a request use 0, /// from 100-199 use 1, 200-299 use 2, etc. /// A UUID to correlate the results when the event is raised public UUID StartDirPlacesSearch(string searchText, int queryStart) { return StartDirPlacesSearch(searchText, DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, ParcelCategory.Any, queryStart); } /// /// Queries the dataserver for parcels of land which are flagged to be shown in search /// /// The event is raised when a response is received from the simulator /// /// A string containing a list of keywords to search for separated by a space character /// A set of flags which can be ORed to modify query options /// such as classified maturity rating. /// The category to search /// Each request is limited to 100 places /// being returned. To get the first 100 result entries of a request use 0, /// from 100-199 use 1, 200-299 use 2, etc. /// A UUID to correlate the results when the event is raised /// /// Search places containing the key words "foo" and "bar" in the "Any" category that are either PG or Adult /// /// UUID searchID = StartDirPlacesSearch("foo bar", DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeAdult, ParcelCategory.Any, 0); /// /// /// /// Additional information on the results can be obtained by using the ParcelManager.InfoRequest method /// public UUID StartDirPlacesSearch(string searchText, DirFindFlags queryFlags, ParcelCategory category, int queryStart) { DirPlacesQueryPacket query = new DirPlacesQueryPacket(); UUID queryID = UUID.Random(); query.AgentData.AgentID = Client.Self.AgentID; query.AgentData.SessionID = Client.Self.SessionID; query.QueryData.Category = (sbyte)category; query.QueryData.QueryFlags = (uint)queryFlags; query.QueryData.QueryID = queryID; query.QueryData.QueryText = Utils.StringToBytes(searchText); query.QueryData.QueryStart = queryStart; query.QueryData.SimName = Utils.StringToBytes(string.Empty); Client.Network.SendPacket(query); return queryID; } /// /// Starts a search for land sales using the directory /// /// The event is raised when a response is received from the simulator /// /// What type of land to search for. Auction, /// estate, mainland, "first land", etc /// The OnDirLandReply event handler must be registered before /// calling this function. There is no way to determine how many /// results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each query. public void StartLandSearch(SearchTypeFlags typeFlags) { StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort, typeFlags, 0, 0, 0); } /// /// Starts a search for land sales using the directory /// /// The event is raised when a response is received from the simulator /// /// What type of land to search for. Auction, /// estate, mainland, "first land", etc /// Maximum price to search for /// Maximum area to search for /// Each request is limited to 100 parcels /// being returned. To get the first 100 parcels of a request use 0, /// from 100-199 use 1, 200-299 use 2, etc. /// The OnDirLandReply event handler must be registered before /// calling this function. There is no way to determine how many /// results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each query. public void StartLandSearch(SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) { StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByPrice | DirFindFlags.LimitByArea, typeFlags, priceLimit, areaLimit, queryStart); } /// /// Send a request to the data server for land sales listings /// /// /// Flags sent to specify query options /// /// Available flags: /// Specify the parcel rating with one or more of the following: /// IncludePG IncludeMature IncludeAdult /// /// Specify the field to pre sort the results with ONLY ONE of the following: /// PerMeterSort NameSort AreaSort PricesSort /// /// Specify the order the results are returned in, if not specified the results are pre sorted in a Descending Order /// SortAsc /// /// Specify additional filters to limit the results with one or both of the following: /// LimitByPrice LimitByArea /// /// Flags can be combined by separating them with the | (pipe) character /// /// Additional details can be found in /// /// What type of land to search for. Auction, /// Estate or Mainland /// Maximum price to search for when the /// DirFindFlags.LimitByPrice flag is specified in findFlags /// Maximum area to search for when the /// DirFindFlags.LimitByArea flag is specified in findFlags /// Each request is limited to 100 parcels /// being returned. To get the first 100 parcels of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// The event will be raised with the response from the simulator /// /// There is no way to determine how many results will be returned, or how many times the callback will be /// fired other than you won't get more than 100 total parcels from /// each reply. /// /// Any land set for sale to either anybody or specific to the connected agent will be included in the /// results if the land is included in the query /// /// /// // request all mainland, any maturity rating that is larger than 512 sq.m /// StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByArea | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, SearchTypeFlags.Mainland, 0, 512, 0); /// public void StartLandSearch(DirFindFlags findFlags, SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) { DirLandQueryPacket query = new DirLandQueryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, QueryData = { Area = areaLimit, Price = priceLimit, QueryStart = queryStart, SearchType = (uint)typeFlags, QueryFlags = (uint)findFlags, QueryID = UUID.Random() } }; Client.Network.SendPacket(query); } /// /// Search for Groups /// /// The name or portion of the name of the group you wish to search for /// Start from the match number /// public UUID StartGroupSearch(string searchText, int queryStart) { return StartGroupSearch(searchText, queryStart, DirFindFlags.Groups | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult); } /// /// Search for Groups /// /// The name or portion of the name of the group you wish to search for /// Start from the match number /// Search flags /// public UUID StartGroupSearch(string searchText, int queryStart, DirFindFlags flags) { DirFindQueryPacket find = new DirFindQueryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, QueryData = { QueryFlags = (uint)flags, QueryText = Utils.StringToBytes(searchText), QueryID = UUID.Random(), QueryStart = queryStart } }; Client.Network.SendPacket(find); return find.QueryData.QueryID; } /// /// Search the People directory for other avatars /// /// The name or portion of the name of the avatar you wish to search for /// /// public UUID StartPeopleSearch(string searchText, int queryStart) { DirFindQueryPacket find = new DirFindQueryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, QueryData = { QueryFlags = (uint)DirFindFlags.People, QueryText = Utils.StringToBytes(searchText), QueryID = UUID.Random(), QueryStart = queryStart } }; Client.Network.SendPacket(find); return find.QueryData.QueryID; } /// /// Search Places for parcels of land you personally own /// public UUID StartPlacesSearch() { return StartPlacesSearch(DirFindFlags.AgentOwned, ParcelCategory.Any, String.Empty, String.Empty, UUID.Zero, UUID.Random()); } /// /// Searches Places for land owned by the specified group /// /// ID of the group you want to receive land list for (You must be a member of the group) /// Transaction (Query) ID which can be associated with results from your request. public UUID StartPlacesSearch(UUID groupID) { return StartPlacesSearch(DirFindFlags.GroupOwned, ParcelCategory.Any, String.Empty, String.Empty, groupID, UUID.Random()); } /// /// Search the Places directory for parcels that are listed in search and contain the specified keywords /// /// A string containing the keywords to search for /// Transaction (Query) ID which can be associated with results from your request. public UUID StartPlacesSearch(string searchText) { return StartPlacesSearch(DirFindFlags.DwellSort | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, ParcelCategory.Any, searchText, String.Empty, UUID.Zero, UUID.Random()); } /// /// Search Places - All Options /// /// One of the Values from the DirFindFlags struct, ie: AgentOwned, GroupOwned, etc. /// One of the values from the SearchCategory Struct, ie: Any, Linden, Newcomer /// A string containing a list of keywords to search for separated by a space character /// String Simulator Name to search in /// LLUID of group you want to receive results for /// Transaction (Query) ID which can be associated with results from your request. /// Transaction (Query) ID which can be associated with results from your request. public UUID StartPlacesSearch(DirFindFlags findFlags, ParcelCategory searchCategory, string searchText, string simulatorName, UUID groupID, UUID transactionID) { PlacesQueryPacket find = new PlacesQueryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID, QueryID = groupID }, TransactionData = { TransactionID = transactionID }, QueryData = { QueryText = Utils.StringToBytes(searchText), QueryFlags = (uint)findFlags, Category = (sbyte)searchCategory, SimName = Utils.StringToBytes(simulatorName) } }; Client.Network.SendPacket(find); return transactionID; } /// /// Search All Events with specifid searchText in all categories, includes PG, Mature and Adult /// /// A string containing a list of keywords to search for separated by a space character /// Each request is limited to 100 entries /// being returned. To get the first group of entries of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// UUID of query to correlate results in callback. public UUID StartEventsSearch(string searchText, uint queryStart) { return StartEventsSearch(searchText, DirFindFlags.DateEvents | DirFindFlags.IncludePG | DirFindFlags.IncludeMature | DirFindFlags.IncludeAdult, "u", queryStart, EventCategories.All); } /// /// Search Events /// /// A string containing a list of keywords to search for separated by a space character /// One or more of the following flags: DateEvents, IncludePG, IncludeMature, IncludeAdult /// from the Enum /// /// Multiple flags can be combined by separating the flags with the | (pipe) character /// "u" for in-progress and upcoming events, -or- number of days since/until event is scheduled /// For example "0" = Today, "1" = tomorrow, "2" = following day, "-1" = yesterday, etc. /// Each request is limited to 100 entries /// being returned. To get the first group of entries of a request use 0, /// from 100-199 use 100, 200-299 use 200, etc. /// EventCategory event is listed under. /// UUID of query to correlate results in callback. public UUID StartEventsSearch(string searchText, DirFindFlags queryFlags, string eventDay, uint queryStart, EventCategories category) { DirFindQueryPacket find = new DirFindQueryPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID } }; UUID queryID = UUID.Random(); find.QueryData.QueryID = queryID; find.QueryData.QueryText = Utils.StringToBytes(eventDay + "|" + (int)category + "|" + searchText); find.QueryData.QueryFlags = (uint)queryFlags; find.QueryData.QueryStart = (int)queryStart; Client.Network.SendPacket(find); return queryID; } /// Requests Event Details /// ID of Event returned from the method public void EventInfoRequest(uint eventID) { EventInfoRequestPacket find = new EventInfoRequestPacket { AgentData = { AgentID = Client.Self.AgentID, SessionID = Client.Self.SessionID }, EventData = { EventID = eventID } }; Client.Network.SendPacket(find); } #endregion #region Blocking Functions [Obsolete("Use the async StartPeopleSearch method instead")] public bool PeopleSearch(DirFindFlags findFlags, string searchText, int queryStart, int timeoutMS, out List results) { AutoResetEvent searchEvent = new AutoResetEvent(false); UUID id = UUID.Zero; List people = null; EventHandler callback = delegate(object sender, DirPeopleReplyEventArgs e) { if (id == e.QueryID) { people = e.MatchedPeople; searchEvent.Set(); } }; DirPeopleReply += callback; id = StartPeopleSearch(searchText, queryStart); searchEvent.WaitOne(timeoutMS, false); DirPeopleReply -= callback; results = people; return (results != null); } #endregion Blocking Functions #region Packet Handlers /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirClassifiedReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirClassifieds != null) { DirClassifiedReplyPacket reply = (DirClassifiedReplyPacket)e.Packet; List classifieds = new List(); foreach (DirClassifiedReplyPacket.QueryRepliesBlock block in reply.QueryReplies) { Classified classified = new Classified { CreationDate = Utils.UnixTimeToDateTime(block.CreationDate), ExpirationDate = Utils.UnixTimeToDateTime(block.ExpirationDate), Flags = (ClassifiedFlags)block.ClassifiedFlags, ID = block.ClassifiedID, Name = Utils.BytesToString(block.Name), Price = block.PriceForListing }; classifieds.Add(classified); } OnDirClassifieds(new DirClassifiedsReplyEventArgs(classifieds)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirLandReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirLandReply != null) { List parcelsForSale = new List(); DirLandReplyPacket reply = (DirLandReplyPacket)e.Packet; foreach (DirLandReplyPacket.QueryRepliesBlock block in reply.QueryReplies) { DirectoryParcel dirParcel = new DirectoryParcel { ActualArea = block.ActualArea, ID = block.ParcelID, Name = Utils.BytesToString(block.Name), SalePrice = block.SalePrice, Auction = block.Auction, ForSale = block.ForSale }; parcelsForSale.Add(dirParcel); } OnDirLand(new DirLandReplyEventArgs(parcelsForSale)); } } /// Process an incoming event message /// The Unique Capabilities Key /// The event message containing the data /// The simulator the message originated from protected void DirLandReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { if (m_DirLandReply != null) { List parcelsForSale = new List(); DirLandReplyMessage reply = (DirLandReplyMessage)message; foreach (DirLandReplyMessage.QueryReply block in reply.QueryReplies) { DirectoryParcel dirParcel = new DirectoryParcel { ActualArea = block.ActualArea, ID = block.ParcelID, Name = block.Name, SalePrice = block.SalePrice, Auction = block.Auction, ForSale = block.ForSale }; parcelsForSale.Add(dirParcel); } OnDirLand(new DirLandReplyEventArgs(parcelsForSale)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirPeopleReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirPeople != null) { DirPeopleReplyPacket peopleReply = e.Packet as DirPeopleReplyPacket; List matches = new List(peopleReply.QueryReplies.Length); foreach (DirPeopleReplyPacket.QueryRepliesBlock reply in peopleReply.QueryReplies) { AgentSearchData searchData = new AgentSearchData { Online = reply.Online, FirstName = Utils.BytesToString(reply.FirstName), LastName = Utils.BytesToString(reply.LastName), AgentID = reply.AgentID }; matches.Add(searchData); } OnDirPeople(new DirPeopleReplyEventArgs(peopleReply.QueryData.QueryID, matches)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirGroupsReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirGroups != null) { Packet packet = e.Packet; DirGroupsReplyPacket groupsReply = (DirGroupsReplyPacket)packet; List matches = new List(groupsReply.QueryReplies.Length); foreach (DirGroupsReplyPacket.QueryRepliesBlock reply in groupsReply.QueryReplies) { GroupSearchData groupsData = new GroupSearchData { GroupID = reply.GroupID, GroupName = Utils.BytesToString(reply.GroupName), Members = reply.Members }; matches.Add(groupsData); } OnDirGroups(new DirGroupsReplyEventArgs(groupsReply.QueryData.QueryID, matches)); } } /// Process an incoming event message /// The Unique Capabilities Key /// The event message containing the data /// The simulator the message originated from protected void PlacesReplyEventHandler(string capsKey, IMessage message, Simulator simulator) { if (m_Places != null) { PlacesReplyMessage replyMessage = (PlacesReplyMessage)message; List places = new List(); foreach (var query in replyMessage.QueryDataBlocks) { PlacesSearchData place = new PlacesSearchData { ActualArea = query.ActualArea, BillableArea = query.BillableArea, Desc = query.Description, Dwell = query.Dwell, Flags = (DirectoryManager.PlacesFlags)(byte)query.Flags, GlobalX = query.GlobalX, GlobalY = query.GlobalY, GlobalZ = query.GlobalZ, Name = query.Name, OwnerID = query.OwnerID, Price = query.Price, SimName = query.SimName, SnapshotID = query.SnapShotID, SKU = query.ProductSku }; places.Add(place); } OnPlaces(new PlacesReplyEventArgs(replyMessage.QueryID, places)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void PlacesReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_Places != null) { Packet packet = e.Packet; PlacesReplyPacket placesReply = packet as PlacesReplyPacket; List places = new List(); foreach (PlacesReplyPacket.QueryDataBlock block in placesReply.QueryData) { PlacesSearchData place = new PlacesSearchData { OwnerID = block.OwnerID, Name = Utils.BytesToString(block.Name), Desc = Utils.BytesToString(block.Desc), ActualArea = block.ActualArea, BillableArea = block.BillableArea, Flags = (PlacesFlags)block.Flags, GlobalX = block.GlobalX, GlobalY = block.GlobalY, GlobalZ = block.GlobalZ, SimName = Utils.BytesToString(block.SimName), SnapshotID = block.SnapshotID, Dwell = block.Dwell, Price = block.Price }; places.Add(place); } OnPlaces(new PlacesReplyEventArgs(placesReply.TransactionData.TransactionID, places)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void EventsReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirEvents != null) { Packet packet = e.Packet; DirEventsReplyPacket eventsReply = (DirEventsReplyPacket)packet; List matches = new List(eventsReply.QueryReplies.Length); foreach (DirEventsReplyPacket.QueryRepliesBlock reply in eventsReply.QueryReplies) { EventsSearchData eventsData = new EventsSearchData { Owner = reply.OwnerID, Name = Utils.BytesToString(reply.Name), ID = reply.EventID, Date = Utils.BytesToString(reply.Date), Time = reply.UnixTime, Flags = (EventFlags)reply.EventFlags }; matches.Add(eventsData); } OnDirEvents(new DirEventsReplyEventArgs(eventsReply.QueryData.QueryID, matches)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void EventInfoReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_EventInfoReply != null) { Packet packet = e.Packet; EventInfoReplyPacket eventReply = (EventInfoReplyPacket)packet; EventInfo evinfo = new EventInfo { ID = eventReply.EventData.EventID, Name = Utils.BytesToString(eventReply.EventData.Name), Desc = Utils.BytesToString(eventReply.EventData.Desc), Amount = eventReply.EventData.Amount, Category = (EventCategories)Utils.BytesToUInt(eventReply.EventData.Category), Cover = eventReply.EventData.Cover, Creator = (UUID)Utils.BytesToString(eventReply.EventData.Creator), Date = Utils.BytesToString(eventReply.EventData.Date), DateUTC = eventReply.EventData.DateUTC, Duration = eventReply.EventData.Duration, Flags = (EventFlags)eventReply.EventData.EventFlags, SimName = Utils.BytesToString(eventReply.EventData.SimName), GlobalPos = eventReply.EventData.GlobalPos }; OnEventInfo(new EventInfoReplyEventArgs(evinfo)); } } /// Process an incoming packet and raise the appropriate events /// The sender /// The EventArgs object containing the packet data protected void DirPlacesReplyHandler(object sender, PacketReceivedEventArgs e) { if (m_DirPlaces != null) { Packet packet = e.Packet; DirPlacesReplyPacket reply = (DirPlacesReplyPacket)packet; List result = reply.QueryReplies.Select(t => new DirectoryParcel { ID = t.ParcelID, Name = Utils.BytesToString(t.Name), Dwell = t.Dwell, Auction = t.Auction, ForSale = t.ForSale }) .ToList(); OnDirPlaces(new DirPlacesReplyEventArgs(reply.QueryData[0].QueryID, result)); } } #endregion Packet Handlers } #region DirectoryManager EventArgs Classes /// Contains the Event data returned from the data server from an EventInfoRequest public class EventInfoReplyEventArgs : EventArgs { /// /// A single EventInfo object containing the details of an event /// public DirectoryManager.EventInfo MatchedEvent { get; } /// Construct a new instance of the EventInfoReplyEventArgs class /// A single EventInfo object containing the details of an event public EventInfoReplyEventArgs(DirectoryManager.EventInfo matchedEvent) { this.MatchedEvent = matchedEvent; } } /// Contains the "Event" detail data returned from the data server public class DirEventsReplyEventArgs : EventArgs { /// The ID returned by public UUID QueryID { get; } /// A list of "Events" returned by the data server public List MatchedEvents { get; } /// Construct a new instance of the DirEventsReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing the "Events" returned by the search query public DirEventsReplyEventArgs(UUID queryID, List matchedEvents) { this.QueryID = queryID; this.MatchedEvents = matchedEvents; } } /// Contains the "Event" list data returned from the data server public class PlacesReplyEventArgs : EventArgs { /// The ID returned by public UUID QueryID { get; } /// A list of "Places" returned by the data server public List MatchedPlaces { get; } /// Construct a new instance of PlacesReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing the "Places" returned by the data server query public PlacesReplyEventArgs(UUID queryID, List matchedPlaces) { this.QueryID = queryID; this.MatchedPlaces = matchedPlaces; } } /// Contains the places data returned from the data server public class DirPlacesReplyEventArgs : EventArgs { /// The ID returned by public UUID QueryID { get; } /// A list containing Places data returned by the data server public List MatchedParcels { get; } /// Construct a new instance of the DirPlacesReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list containing land data returned by the data server public DirPlacesReplyEventArgs(UUID queryID, List matchedParcels) { this.QueryID = queryID; this.MatchedParcels = matchedParcels; } } /// Contains the classified data returned from the data server public class DirClassifiedsReplyEventArgs : EventArgs { /// A list containing Classified Ads returned by the data server public List Classifieds { get; } /// Construct a new instance of the DirClassifiedsReplyEventArgs class /// A list of classified ad data returned from the data server public DirClassifiedsReplyEventArgs(List classifieds) { this.Classifieds = classifieds; } } /// Contains the group data returned from the data server public class DirGroupsReplyEventArgs : EventArgs { /// The ID returned by public UUID QueryID { get; } /// A list containing Groups data returned by the data server public List MatchedGroups { get; } /// Construct a new instance of the DirGroupsReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list of groups data returned by the data server public DirGroupsReplyEventArgs(UUID queryID, List matchedGroups) { this.QueryID = queryID; this.MatchedGroups = matchedGroups; } } /// Contains the people data returned from the data server public class DirPeopleReplyEventArgs : EventArgs { /// The ID returned by public UUID QueryID { get; } /// A list containing People data returned by the data server public List MatchedPeople { get; } /// Construct a new instance of the DirPeopleReplyEventArgs class /// The ID of the query returned by the data server. /// This will correlate to the ID returned by the method /// A list of people data returned by the data server public DirPeopleReplyEventArgs(UUID queryID, List matchedPeople) { this.QueryID = queryID; this.MatchedPeople = matchedPeople; } } /// Contains the land sales data returned from the data server public class DirLandReplyEventArgs : EventArgs { /// A list containing land forsale data returned by the data server public List DirParcels { get; } /// Construct a new instance of the DirLandReplyEventArgs class /// A list of parcels for sale returned by the data server public DirLandReplyEventArgs(List dirParcels) { this.DirParcels = dirParcels; } } #endregion }