/* * Copyright (c) 2006-2008, 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.Text; using System.Collections; using System.Collections.Generic; using System.Threading; using OpenMetaverse.StructuredData; using OpenMetaverse.Capabilities; using OpenMetaverse.Packets; namespace OpenMetaverse { /// /// /// public enum GridLayerType : uint { /// Objects and terrain are shown Objects = 0, /// Only the terrain is shown, no objects Terrain = 1, /// Overlay showing land for sale and for auction LandForSale = 2 } /// /// /// public enum GridItemType : uint { Telehub = 1, PgEvent = 2, MatureEvent = 3, Popular = 4, AgentLocations = 6, LandForSale = 7, Classified = 8 } /// /// Information about a region on the grid map /// public struct GridRegion { /// Sim X position on World Map public int X; /// Sim Y position on World Map public int Y; /// Sim Name (NOTE: In lowercase!) public string Name; /// public Simulator.SimAccess Access; /// Appears to always be zero (None) public Simulator.RegionFlags RegionFlags; /// Sim's defined Water Height public byte WaterHeight; /// public byte Agents; /// UUID of the World Map image public UUID MapImageID; /// Unique identifier for this region, a combination of the X /// and Y position public ulong RegionHandle; /// /// /// /// public override string ToString() { StringBuilder output = new StringBuilder("GridRegion: "); output.Append(Name); output.Append(Helpers.NewLine); output.Append("RegionHandle: " + RegionHandle); output.Append(Helpers.NewLine); output.Append(String.Format("X: {0} Y: {1}", X, Y)); output.Append(Helpers.NewLine); output.Append("MapImageID: " + MapImageID.ToString()); output.Append(Helpers.NewLine); output.Append("Access: " + Access); output.Append(Helpers.NewLine); output.Append("RegionFlags: " + RegionFlags); output.Append(Helpers.NewLine); output.Append("WaterHeight: " + WaterHeight); output.Append(Helpers.NewLine); output.Append("Agents: " + Agents); return output.ToString(); } /// /// /// /// public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); } /// /// /// /// /// public override bool Equals(object obj) { if (obj is GridRegion) return Equals((GridRegion)obj); else return false; } private bool Equals(GridRegion region) { return (this.X == region.X && this.Y == region.Y); } } /// /// Visual chunk of the grid map /// public struct GridLayer { public int Bottom; public int Left; public int Top; public int Right; public UUID ImageID; public bool ContainsRegion(int x, int y) { return (x >= Left && x <= Right && y >= Bottom && y <= Top); } } public abstract class GridItem { } public class GridAgentLocation : GridItem { public uint GlobalX; public uint GlobalY; public int AvatarCount; public string Identifier; public uint LocalX { get { return GlobalX % 256; } } public uint LocalY { get { return GlobalY % 256; } } public ulong RegionHandle { get { return Helpers.UIntsToLong((uint)(GlobalX - (GlobalX % 256)), (uint)(GlobalY - (GlobalY % 256))); } } } /// /// Manages grid-wide tasks such as the world map /// public class GridManager { /// /// /// /// public delegate void CoarseLocationUpdateCallback(Simulator sim); /// /// /// /// public delegate void GridRegionCallback(GridRegion region); /// /// /// /// public delegate void GridLayerCallback(GridLayer layer); /// /// /// /// /// public delegate void GridItemsCallback(GridItemType type, List items); /// /// /// /// /// public delegate void RegionHandleReplyCallback(UUID regionID, ulong regionHandle); /// Triggered when coarse locations (minimap dots) are updated by the simulator public event CoarseLocationUpdateCallback OnCoarseLocationUpdate; /// Triggered when a new region is discovered through GridManager public event GridRegionCallback OnGridRegion; /// public event GridLayerCallback OnGridLayer; /// public event GridItemsCallback OnGridItems; /// public event RegionHandleReplyCallback OnRegionHandleReply; /// Unknown public float SunPhase { get { return sunPhase; } } /// Current direction of the sun public Vector3 SunDirection { get { return sunDirection; } } /// Current angular velocity of the sun public Vector3 SunAngVelocity { get { return sunAngVelocity; } } /// A dictionary of all the regions, indexed by region name internal Dictionary Regions = new Dictionary(); /// A dictionary of all the regions, indexed by region handle internal Dictionary RegionsByHandle = new Dictionary(); private GridClient Client; private float sunPhase; private Vector3 sunDirection; private Vector3 sunAngVelocity; /// /// Constructor /// /// Instance of GridClient object to associate with this GridManager instance public GridManager(GridClient client) { Client = client; Client.Network.RegisterCallback(PacketType.MapBlockReply, new NetworkManager.PacketCallback(MapBlockReplyHandler)); Client.Network.RegisterCallback(PacketType.MapItemReply, new NetworkManager.PacketCallback(MapItemReplyHandler)); Client.Network.RegisterCallback(PacketType.SimulatorViewerTimeMessage, new NetworkManager.PacketCallback(TimeMessageHandler)); Client.Network.RegisterCallback(PacketType.CoarseLocationUpdate, new NetworkManager.PacketCallback(CoarseLocationHandler)); Client.Network.RegisterCallback(PacketType.RegionIDAndHandleReply, new NetworkManager.PacketCallback(RegionHandleReplyHandler)); } /// /// /// /// public void RequestMapLayer(GridLayerType layer) { Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("MapLayer"); if (url != null) { LLSDMap body = new LLSDMap(); body["Flags"] = LLSD.FromInteger((int)layer); CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(MapLayerResponseHandler); request.StartRequest(body); } } /// /// /// /// /// public void RequestMapRegion(string regionName, GridLayerType layer) { MapNameRequestPacket request = new MapNameRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.AgentData.Flags = (uint)layer; request.AgentData.EstateID = 0; // Filled in on the sim request.AgentData.Godlike = false; // Filled in on the sim request.NameData.Name = Helpers.StringToField(regionName.ToLower()); Client.Network.SendPacket(request); } /// /// /// /// /// /// /// /// /// public void RequestMapBlocks(GridLayerType layer, ushort minX, ushort minY, ushort maxX, ushort maxY, bool returnNonExistent) { MapBlockRequestPacket request = new MapBlockRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.AgentData.Flags = (uint)layer; request.AgentData.Flags |= (uint)(returnNonExistent ? 0x10000 : 0); request.AgentData.EstateID = 0; // Filled in at the simulator request.AgentData.Godlike = false; // Filled in at the simulator request.PositionData.MinX = minX; request.PositionData.MinY = minY; request.PositionData.MaxX = maxX; request.PositionData.MaxY = maxY; Client.Network.SendPacket(request); } /// /// /// /// /// /// /// /// public List MapItems(ulong regionHandle, GridItemType item, GridLayerType layer, int timeoutMS) { List itemList = null; AutoResetEvent itemsEvent = new AutoResetEvent(false); GridItemsCallback callback = delegate(GridItemType type, List items) { if (type == GridItemType.AgentLocations) { itemList = items; itemsEvent.Set(); } }; OnGridItems += callback; RequestMapItems(regionHandle, item, layer); itemsEvent.WaitOne(timeoutMS, false); OnGridItems -= callback; return itemList; } /// /// /// /// /// /// public void RequestMapItems(ulong regionHandle, GridItemType item, GridLayerType layer) { MapItemRequestPacket request = new MapItemRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.AgentData.Flags = (uint)layer; request.AgentData.Godlike = false; // Filled in on the sim request.AgentData.EstateID = 0; // Filled in on the sim request.RequestData.ItemType = (uint)item; request.RequestData.RegionHandle = regionHandle; Client.Network.SendPacket(request); } /// /// Request data for all mainland (Linden managed) simulators /// public void RequestMainlandSims(GridLayerType layer) { RequestMapBlocks(layer, 0, 0, 65535, 65535, false); } /// /// Request the region handle for the specified region UUID /// /// UUID of the region to look up public void RequestRegionHandle(UUID regionID) { RegionHandleRequestPacket request = new RegionHandleRequestPacket(); request.RequestBlock = new RegionHandleRequestPacket.RequestBlockBlock(); request.RequestBlock.RegionID = regionID; Client.Network.SendPacket(request); } /// /// Get grid region information using the region name, this function /// will block until it can find the region or gives up /// /// Name of sim you're looking for /// Layer that you are requesting /// Will contain a GridRegion for the sim you're /// looking for if successful, otherwise an empty structure /// True if the GridRegion was successfully fetched, otherwise /// false public bool GetGridRegion(string name, GridLayerType layer, out GridRegion region) { if (String.IsNullOrEmpty(name)) { Logger.Log("GetGridRegion called with a null or empty region name", Helpers.LogLevel.Error, Client); region = new GridRegion(); return false; } // All lookups are done using lowercase sim names name = name.ToLower(); if (Regions.ContainsKey(name)) { // We already have this GridRegion structure region = Regions[name]; return true; } else { AutoResetEvent regionEvent = new AutoResetEvent(false); GridRegionCallback callback = delegate(GridRegion gridRegion) { if (gridRegion.Name == name) regionEvent.Set(); }; OnGridRegion += callback; RequestMapRegion(name, layer); regionEvent.WaitOne(Client.Settings.MAP_REQUEST_TIMEOUT, false); OnGridRegion -= callback; if (Regions.ContainsKey(name)) { // The region was found after our request region = Regions[name]; return true; } else { Logger.Log("Couldn't find region " + name, Helpers.LogLevel.Warning, Client); region = new GridRegion(); return false; } } } private void MapLayerResponseHandler(CapsClient client, LLSD result, Exception error) { LLSDMap body = (LLSDMap)result; LLSDArray layerData = (LLSDArray)body["LayerData"]; if (OnGridLayer != null) { for (int i = 0; i < layerData.Count; i++) { LLSDMap thisLayerData = (LLSDMap)layerData[i]; GridLayer layer; layer.Bottom = thisLayerData["Bottom"].AsInteger(); layer.Left = thisLayerData["Left"].AsInteger(); layer.Top = thisLayerData["Top"].AsInteger(); layer.Right = thisLayerData["Right"].AsInteger(); layer.ImageID = thisLayerData["ImageID"].AsUUID(); try { OnGridLayer(layer); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } if (body.ContainsKey("MapBlocks")) { // TODO: At one point this will become activated Logger.Log("Got MapBlocks through CAPS, please finish this function!", Helpers.LogLevel.Error, Client); } } /// /// Populate Grid info based on data from MapBlockReplyPacket /// /// Incoming MapBlockReplyPacket packet /// Unused private void MapBlockReplyHandler(Packet packet, Simulator simulator) { MapBlockReplyPacket map = (MapBlockReplyPacket)packet; foreach (MapBlockReplyPacket.DataBlock block in map.Data) { if (block.X != 0 && block.Y != 0) { GridRegion region; region.X = block.X; region.Y = block.Y; region.Name = Helpers.FieldToUTF8String(block.Name); // RegionFlags seems to always be zero here? region.RegionFlags = (Simulator.RegionFlags)block.RegionFlags; region.WaterHeight = block.WaterHeight; region.Agents = block.Agents; region.Access = (Simulator.SimAccess)block.Access; region.MapImageID = block.MapImageID; region.RegionHandle = Helpers.UIntsToLong((uint)(region.X * 256), (uint)(region.Y * 256)); lock (Regions) { Regions[region.Name.ToLower()] = region; RegionsByHandle[region.RegionHandle] = region; } if (OnGridRegion != null) { try { OnGridRegion(region); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } } private void MapItemReplyHandler(Packet packet, Simulator simulator) { if (OnGridItems != null) { MapItemReplyPacket reply = (MapItemReplyPacket)packet; GridItemType type = (GridItemType)reply.RequestData.ItemType; List items = new List(); for (int i = 0; i < reply.Data.Length; i++) { string name = Helpers.FieldToUTF8String(reply.Data[i].Name); switch (type) { case GridItemType.AgentLocations: GridAgentLocation location = new GridAgentLocation(); location.GlobalX = reply.Data[i].X; location.GlobalY = reply.Data[i].Y; location.Identifier = name; location.AvatarCount = reply.Data[i].Extra; items.Add(location); break; case GridItemType.Classified: //FIXME: Logger.Log("FIXME", Helpers.LogLevel.Error, Client); break; case GridItemType.LandForSale: //FIXME: Logger.Log("FIXME", Helpers.LogLevel.Error, Client); break; case GridItemType.MatureEvent: case GridItemType.PgEvent: //FIXME: Logger.Log("FIXME", Helpers.LogLevel.Error, Client); break; case GridItemType.Popular: //FIXME: Logger.Log("FIXME", Helpers.LogLevel.Error, Client); break; case GridItemType.Telehub: //FIXME: Logger.Log("FIXME", Helpers.LogLevel.Error, Client); break; default: Logger.Log("Unknown map item type " + type, Helpers.LogLevel.Warning, Client); break; } } try { OnGridItems(type, items); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } /// /// Get sim time from the appropriate packet /// /// Incoming SimulatorViewerTimeMessagePacket from SL /// Unused private void TimeMessageHandler(Packet packet, Simulator simulator) { SimulatorViewerTimeMessagePacket time = (SimulatorViewerTimeMessagePacket)packet; sunPhase = time.TimeInfo.SunPhase; sunDirection = time.TimeInfo.SunDirection; sunAngVelocity = time.TimeInfo.SunAngVelocity; // TODO: Does anyone have a use for the time stuff? } private void CoarseLocationHandler(Packet packet, Simulator simulator) { CoarseLocationUpdatePacket coarse = (CoarseLocationUpdatePacket)packet; lock (simulator.avatarPositions) { simulator.avatarPositions.Clear(); for (int i = 0; i < coarse.Location.Length; i++) { if (i == coarse.Index.You) { simulator.positionIndexYou = i; } else if (i == coarse.Index.Prey) { simulator.positionIndexPrey = i; } simulator.avatarPositions.Add(new Vector3(coarse.Location[i].X, coarse.Location[i].Y, coarse.Location[i].Z)); } if (OnCoarseLocationUpdate != null) { try { OnCoarseLocationUpdate(simulator); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } private void RegionHandleReplyHandler(Packet packet, Simulator simulator) { RegionIDAndHandleReplyPacket reply = (RegionIDAndHandleReplyPacket)packet; if (OnRegionHandleReply != null) { try { OnRegionHandleReply(reply.ReplyBlock.RegionID, reply.ReplyBlock.RegionHandle); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } } }