1874 lines
76 KiB
C#
1874 lines
76 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* 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.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSimulator Project 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 DEVELOPERS ``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 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.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
|
|
using System.Threading;
|
|
using log4net;
|
|
using Nini.Config;
|
|
using OpenMetaverse;
|
|
using OpenMetaverse.Imaging;
|
|
using OpenMetaverse.StructuredData;
|
|
using Mono.Addins;
|
|
using OpenSim.Framework;
|
|
using OpenSim.Framework.Capabilities;
|
|
using OpenSim.Framework.Monitoring;
|
|
using OpenSim.Framework.Servers;
|
|
using OpenSim.Framework.Servers.HttpServer;
|
|
using OpenSim.Region.Framework.Interfaces;
|
|
using OpenSim.Region.Framework.Scenes;
|
|
using OpenSim.Region.CoreModules.World.Land;
|
|
using Caps=OpenSim.Framework.Capabilities.Caps;
|
|
using OSDArray=OpenMetaverse.StructuredData.OSDArray;
|
|
using OSDMap=OpenMetaverse.StructuredData.OSDMap;
|
|
using GridRegion = OpenSim.Services.Interfaces.GridRegion;
|
|
|
|
namespace OpenSim.Region.CoreModules.World.WorldMap
|
|
{
|
|
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WorldMapModule")]
|
|
public class WorldMapModule : INonSharedRegionModule, IWorldMapModule
|
|
{
|
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
#pragma warning disable 414
|
|
private static string LogHeader = "[WORLD MAP]";
|
|
#pragma warning restore 414
|
|
|
|
private static readonly string DEFAULT_WORLD_MAP_EXPORT_PATH = "exportmap.jpg";
|
|
private static readonly UUID STOP_UUID = UUID.Random();
|
|
|
|
private BlockingCollection<MapRequestState> requests = new BlockingCollection<MapRequestState>();
|
|
|
|
private ManualResetEvent m_mapBlockRequestEvent = new ManualResetEvent(false);
|
|
private Dictionary<UUID, Queue<MapBlockRequestData>> m_mapBlockRequests = new Dictionary<UUID, Queue<MapBlockRequestData>>();
|
|
|
|
private IMapImageGenerator m_mapImageGenerator;
|
|
private IMapImageUploadModule m_mapImageServiceModule;
|
|
|
|
protected Scene m_scene;
|
|
private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>();
|
|
private byte[] myMapImageJPEG;
|
|
protected volatile bool m_Enabled = false;
|
|
private ExpiringCache<string, int> m_blacklistedurls = new ExpiringCache<string, int>();
|
|
private ExpiringCache<ulong, int> m_blacklistedregions = new ExpiringCache<ulong, int>();
|
|
private ExpiringCache<ulong, string> m_cachedRegionMapItemsAddress = new ExpiringCache<ulong, string>();
|
|
private ExpiringCache<ulong, OSDMap> m_cachedRegionMapItemsResponses =
|
|
new ExpiringCache<ulong, OSDMap>();
|
|
private List<UUID> m_rootAgents = new List<UUID>();
|
|
private volatile bool threadrunning = false;
|
|
// expire time for the blacklists in seconds
|
|
protected double expireBlackListTime = 600.0; // 10 minutes
|
|
// expire mapItems responses time in seconds. Throttles requests to regions that do answer
|
|
private const double expireResponsesTime = 120.0; // 2 minutes ?
|
|
//private int CacheRegionsDistance = 256;
|
|
|
|
protected bool m_exportPrintScale = false; // prints the scale of map in meters on exported map
|
|
protected bool m_exportPrintRegionName = false; // prints the region name exported map
|
|
protected bool m_showNPCs = true;
|
|
|
|
#region INonSharedRegionModule Members
|
|
public virtual void Initialise(IConfigSource config)
|
|
{
|
|
string[] configSections = new string[] { "Map", "Startup" };
|
|
|
|
if (Util.GetConfigVarFromSections<string>(
|
|
config, "WorldMapModule", configSections, "WorldMap") == "WorldMap")
|
|
m_Enabled = true;
|
|
|
|
expireBlackListTime = (double)Util.GetConfigVarFromSections<int>(config, "BlacklistTimeout", configSections, 10 * 60);
|
|
|
|
m_exportPrintScale =
|
|
Util.GetConfigVarFromSections<bool>(config, "ExportMapAddScale", configSections, m_exportPrintScale);
|
|
m_exportPrintRegionName =
|
|
Util.GetConfigVarFromSections<bool>(config, "ExportMapAddRegionName", configSections, m_exportPrintRegionName);
|
|
m_showNPCs =
|
|
Util.GetConfigVarFromSections<bool>(config, "ShowNPCs", configSections, m_showNPCs);
|
|
}
|
|
|
|
public virtual void AddRegion(Scene scene)
|
|
{
|
|
if (!m_Enabled)
|
|
return;
|
|
|
|
lock (scene)
|
|
{
|
|
m_scene = scene;
|
|
|
|
m_scene.RegisterModuleInterface<IWorldMapModule>(this);
|
|
|
|
m_scene.AddCommand(
|
|
"Regions", this, "export-map",
|
|
"export-map [<path>]",
|
|
"Save an image of the world map", HandleExportWorldMapConsoleCommand);
|
|
|
|
m_scene.AddCommand(
|
|
"Regions", this, "generate map",
|
|
"generate map",
|
|
"Generates and stores a new maptile.", HandleGenerateMapConsoleCommand);
|
|
|
|
AddHandlers();
|
|
}
|
|
}
|
|
|
|
public virtual void RemoveRegion(Scene scene)
|
|
{
|
|
if (!m_Enabled)
|
|
return;
|
|
|
|
lock (m_scene)
|
|
{
|
|
m_Enabled = false;
|
|
RemoveHandlers();
|
|
m_scene = null;
|
|
}
|
|
}
|
|
|
|
public virtual void RegionLoaded(Scene scene)
|
|
{
|
|
if (!m_Enabled)
|
|
return;
|
|
|
|
m_mapImageGenerator = m_scene.RequestModuleInterface<IMapImageGenerator>();
|
|
m_mapImageServiceModule = m_scene.RequestModuleInterface<IMapImageUploadModule>();
|
|
}
|
|
|
|
public virtual void Close()
|
|
{
|
|
}
|
|
|
|
public Type ReplaceableInterface
|
|
{
|
|
get { return null; }
|
|
}
|
|
|
|
public virtual string Name
|
|
{
|
|
get { return "WorldMapModule"; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
// this has to be called with a lock on m_scene
|
|
protected virtual void AddHandlers()
|
|
{
|
|
myMapImageJPEG = new byte[0];
|
|
|
|
string regionimage = "regionImage" + m_scene.RegionInfo.RegionID.ToString();
|
|
regionimage = regionimage.Replace("-", "");
|
|
m_log.Info("[WORLD MAP]: JPEG Map location: " + m_scene.RegionInfo.ServerURI + "index.php?method=" + regionimage);
|
|
|
|
MainServer.Instance.AddIndexPHPMethodHandler(regionimage, OnHTTPGetMapImage);
|
|
MainServer.Instance.AddSimpleStreamHandler(new SimpleStreamHandler(
|
|
"/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString(), HandleRemoteMapItemRequest));
|
|
|
|
m_scene.EventManager.OnRegisterCaps += OnRegisterCaps;
|
|
m_scene.EventManager.OnNewClient += OnNewClient;
|
|
m_scene.EventManager.OnClientClosed += ClientLoggedOut;
|
|
m_scene.EventManager.OnMakeChildAgent += MakeChildAgent;
|
|
m_scene.EventManager.OnMakeRootAgent += MakeRootAgent;
|
|
m_scene.EventManager.OnRegionUp += OnRegionUp;
|
|
|
|
StartThread(new object());
|
|
}
|
|
|
|
// this has to be called with a lock on m_scene
|
|
protected virtual void RemoveHandlers()
|
|
{
|
|
StopThread();
|
|
|
|
m_scene.EventManager.OnRegionUp -= OnRegionUp;
|
|
m_scene.EventManager.OnMakeRootAgent -= MakeRootAgent;
|
|
m_scene.EventManager.OnMakeChildAgent -= MakeChildAgent;
|
|
m_scene.EventManager.OnClientClosed -= ClientLoggedOut;
|
|
m_scene.EventManager.OnNewClient -= OnNewClient;
|
|
m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps;
|
|
|
|
m_scene.UnregisterModuleInterface<IWorldMapModule>(this);
|
|
|
|
MainServer.Instance.RemoveSimpleStreamHandler("/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString());
|
|
string regionimage = "regionImage" + m_scene.RegionInfo.RegionID.ToString();
|
|
regionimage = regionimage.Replace("-", "");
|
|
MainServer.Instance.RemoveIndexPHPMethodHandler(regionimage);
|
|
}
|
|
|
|
public void OnRegisterCaps(UUID agentID, Caps caps)
|
|
{
|
|
//m_log.DebugFormat("[WORLD MAP]: OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
|
|
caps.RegisterSimpleHandler("MapLayer", new SimpleStreamHandler("/" + UUID.Random(), MapLayerRequest));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Callback for a map layer request
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="agentID"></param>
|
|
/// <param name="caps"></param>
|
|
/// <returns></returns>
|
|
public void MapLayerRequest(IOSHttpRequest request, IOSHttpResponse response)
|
|
{
|
|
// this is wrong but does seem i use
|
|
if(request.HttpMethod != "POST")
|
|
{
|
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
|
return;
|
|
}
|
|
LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse();
|
|
mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse());
|
|
response.RawBuffer = System.Text.Encoding.UTF8.GetBytes(LLSDHelpers.SerialiseLLSDReply(mapResponse));
|
|
response.StatusCode = (int)HttpStatusCode.OK;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="mapReq"></param>
|
|
/// <returns></returns>
|
|
public LLSDMapLayerResponse GetMapLayer(LLSDMapRequest mapReq)
|
|
{
|
|
// m_log.DebugFormat("[WORLD MAP]: MapLayer Request in region: {0}", m_scene.RegionInfo.RegionName);
|
|
LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse();
|
|
mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse());
|
|
return mapResponse;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected static OSDMapLayer GetOSDMapLayerResponse()
|
|
{
|
|
// not sure about this.... 2048 or master 5000 and hack above?
|
|
|
|
OSDMapLayer mapLayer = new OSDMapLayer();
|
|
mapLayer.Right = 30000;
|
|
mapLayer.Top = 30000;
|
|
mapLayer.ImageID = new UUID("00000000-0000-1111-9999-000000000006");
|
|
|
|
return mapLayer;
|
|
}
|
|
#region EventHandlers
|
|
|
|
/// <summary>
|
|
/// Registered for event
|
|
/// </summary>
|
|
/// <param name="client"></param>
|
|
private void OnNewClient(IClientAPI client)
|
|
{
|
|
client.OnRequestMapBlocks += RequestMapBlocks;
|
|
client.OnMapItemRequest += HandleMapItemRequest;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Client logged out, check to see if there are any more root agents in the simulator
|
|
/// If not, stop the mapItemRequest Thread
|
|
/// Event handler
|
|
/// </summary>
|
|
/// <param name="AgentId">AgentID that logged out</param>
|
|
private void ClientLoggedOut(UUID AgentId, Scene scene)
|
|
{
|
|
lock (m_rootAgents)
|
|
{
|
|
m_rootAgents.Remove(AgentId);
|
|
}
|
|
lock (m_mapBlockRequestEvent)
|
|
{
|
|
if (m_mapBlockRequests.ContainsKey(AgentId))
|
|
m_mapBlockRequests.Remove(AgentId);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Starts the MapItemRequest Thread
|
|
/// Note that this only gets started when there are actually agents in the region
|
|
/// Additionally, it gets stopped when there are none.
|
|
/// </summary>
|
|
/// <param name="o"></param>
|
|
private void StartThread(object o)
|
|
{
|
|
if (threadrunning) return;
|
|
threadrunning = true;
|
|
|
|
// m_log.Debug("[WORLD MAP]: Starting remote MapItem request thread");
|
|
|
|
WorkManager.StartThread(
|
|
process,
|
|
string.Format("MapItemRequestThread ({0})", m_scene.RegionInfo.RegionName));
|
|
WorkManager.StartThread(
|
|
MapBlockSendThread,
|
|
string.Format("MapBlockSendThread ({0})", m_scene.RegionInfo.RegionName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueues a 'stop thread' MapRequestState. Causes the MapItemRequest thread to end
|
|
/// </summary>
|
|
private void StopThread()
|
|
{
|
|
MapRequestState st = new MapRequestState();
|
|
st.agentID = STOP_UUID;
|
|
st.EstateID = 0;
|
|
st.flags = 0;
|
|
st.godlike = false;
|
|
st.itemtype = 0;
|
|
st.regionhandle = 0;
|
|
|
|
requests.Add(st);
|
|
|
|
MapBlockRequestData req = new MapBlockRequestData();
|
|
|
|
req.client = null;
|
|
req.minX = 0;
|
|
req.maxX = 0;
|
|
req.minY = 0;
|
|
req.maxY = 0;
|
|
req.flags = 0;
|
|
|
|
lock (m_mapBlockRequestEvent)
|
|
{
|
|
m_mapBlockRequests[UUID.Zero] = new Queue<MapBlockRequestData>();
|
|
m_mapBlockRequests[UUID.Zero].Enqueue(req);
|
|
m_mapBlockRequestEvent.Set();
|
|
}
|
|
}
|
|
|
|
public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags,
|
|
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
|
{
|
|
// m_log.DebugFormat("[WORLD MAP]: Handle MapItem request {0} {1}", regionhandle, itemtype);
|
|
|
|
lock (m_rootAgents)
|
|
{
|
|
if (!m_rootAgents.Contains(remoteClient.AgentId))
|
|
return;
|
|
}
|
|
|
|
// local or remote request?
|
|
if (regionhandle != 0 && regionhandle != m_scene.RegionInfo.RegionHandle)
|
|
{
|
|
// its Remote Map Item Request
|
|
// ensures that the blockingqueue doesn't get borked if the GetAgents() timing changes.
|
|
RequestMapItems("", remoteClient.AgentId, flags, EstateID, godlike, itemtype, regionhandle);
|
|
return;
|
|
}
|
|
|
|
uint xstart = 0;
|
|
uint ystart = 0;
|
|
Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle, out xstart, out ystart);
|
|
|
|
// its about this region...
|
|
|
|
List<mapItemReply> mapitems = new List<mapItemReply>();
|
|
mapItemReply mapitem = new mapItemReply();
|
|
|
|
// viewers only ask for green dots to each region now
|
|
// except at login with regionhandle 0
|
|
// possible on some other rare ocasions
|
|
// use previous hack of sending all items with the green dots
|
|
|
|
bool adultRegion;
|
|
if (regionhandle == 0)
|
|
{
|
|
switch (itemtype)
|
|
{
|
|
case (int)GridItemType.AgentLocations:
|
|
// Service 6 right now (MAP_ITEM_AGENTS_LOCATION; green dots)
|
|
|
|
int tc = Environment.TickCount;
|
|
if (m_scene.GetRootAgentCount() <= 1) //own position is not sent
|
|
{
|
|
mapitem = new mapItemReply(
|
|
xstart + 1,
|
|
ystart + 1,
|
|
UUID.Zero,
|
|
Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()),
|
|
0, 0);
|
|
mapitems.Add(mapitem);
|
|
}
|
|
else
|
|
{
|
|
m_scene.ForEachRootScenePresence(delegate (ScenePresence sp)
|
|
{
|
|
// Don't send a green dot for yourself
|
|
if (sp.UUID != remoteClient.AgentId)
|
|
{
|
|
if (sp.PresenceType == PresenceType.Npc && !m_showNPCs)
|
|
return;
|
|
|
|
mapitem = new mapItemReply(
|
|
xstart + (uint)sp.AbsolutePosition.X,
|
|
ystart + (uint)sp.AbsolutePosition.Y,
|
|
UUID.Zero,
|
|
Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()),
|
|
1, 0);
|
|
mapitems.Add(mapitem);
|
|
}
|
|
});
|
|
}
|
|
remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
|
|
break;
|
|
|
|
case (int)GridItemType.Telehub:
|
|
// Service 1 (MAP_ITEM_TELEHUB)
|
|
|
|
SceneObjectGroup sog = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject);
|
|
if (sog != null)
|
|
{
|
|
mapitem = new mapItemReply(
|
|
xstart + (uint)sog.AbsolutePosition.X,
|
|
ystart + (uint)sog.AbsolutePosition.Y,
|
|
UUID.Zero,
|
|
sog.Name,
|
|
0, // color (not used)
|
|
0 // 0 = telehub / 1 = infohub
|
|
);
|
|
mapitems.Add(mapitem);
|
|
remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
|
|
}
|
|
break;
|
|
|
|
case (int)GridItemType.AdultLandForSale:
|
|
case (int)GridItemType.LandForSale:
|
|
|
|
// Service 7 (MAP_ITEM_LAND_FOR_SALE)
|
|
adultRegion = m_scene.RegionInfo.RegionSettings.Maturity == 2;
|
|
if (adultRegion)
|
|
{
|
|
if (itemtype == (int)GridItemType.LandForSale)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (itemtype == (int)GridItemType.AdultLandForSale)
|
|
break;
|
|
}
|
|
|
|
// Parcels
|
|
ILandChannel landChannel = m_scene.LandChannel;
|
|
List<ILandObject> parcels = landChannel.AllParcels();
|
|
|
|
if ((parcels != null) && (parcels.Count >= 1))
|
|
{
|
|
foreach (ILandObject parcel_interface in parcels)
|
|
{
|
|
// Play it safe
|
|
if (!(parcel_interface is LandObject))
|
|
continue;
|
|
|
|
LandObject land = (LandObject)parcel_interface;
|
|
LandData parcel = land.LandData;
|
|
|
|
// Show land for sale
|
|
if ((parcel.Flags & (uint)ParcelFlags.ForSale) == (uint)ParcelFlags.ForSale)
|
|
{
|
|
Vector3 min = parcel.AABBMin;
|
|
Vector3 max = parcel.AABBMax;
|
|
float x = (min.X + max.X) / 2;
|
|
float y = (min.Y + max.Y) / 2;
|
|
mapitem = new mapItemReply(
|
|
xstart + (uint)x,
|
|
ystart + (uint)y,
|
|
parcel.GlobalID,
|
|
parcel.Name,
|
|
parcel.Area,
|
|
parcel.SalePrice
|
|
);
|
|
mapitems.Add(mapitem);
|
|
}
|
|
}
|
|
}
|
|
remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags);
|
|
break;
|
|
|
|
case (uint)GridItemType.PgEvent:
|
|
case (uint)GridItemType.MatureEvent:
|
|
case (uint)GridItemType.AdultEvent:
|
|
case (uint)GridItemType.Classified:
|
|
case (uint)GridItemType.Popular:
|
|
// TODO
|
|
// just dont not cry about them
|
|
break;
|
|
|
|
default:
|
|
// unkown map item type
|
|
m_log.DebugFormat("[WORLD MAP]: Unknown MapItem type {1}", itemtype);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// send all items till we get a better fix
|
|
|
|
// Service 6 right now (MAP_ITEM_AGENTS_LOCATION; green dots)
|
|
|
|
int tc = Environment.TickCount;
|
|
if (m_scene.GetRootAgentCount() <= 1) // own is not sent
|
|
{
|
|
mapitem = new mapItemReply(
|
|
xstart + 1,
|
|
ystart + 1,
|
|
UUID.Zero,
|
|
Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()),
|
|
0, 0);
|
|
mapitems.Add(mapitem);
|
|
}
|
|
else
|
|
{
|
|
m_scene.ForEachRootScenePresence(delegate (ScenePresence sp)
|
|
{
|
|
// Don't send a green dot for yourself
|
|
if (sp.UUID != remoteClient.AgentId)
|
|
{
|
|
if (!m_showNPCs && sp.PresenceType == PresenceType.Npc)
|
|
return;
|
|
|
|
mapitem = new mapItemReply(
|
|
xstart + (uint)sp.AbsolutePosition.X,
|
|
ystart + (uint)sp.AbsolutePosition.Y,
|
|
UUID.Zero,
|
|
Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()),
|
|
1, 0);
|
|
mapitems.Add(mapitem);
|
|
}
|
|
});
|
|
}
|
|
remoteClient.SendMapItemReply(mapitems.ToArray(), 6, flags);
|
|
mapitems.Clear();
|
|
|
|
// Service 1 (MAP_ITEM_TELEHUB)
|
|
|
|
SceneObjectGroup sog = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject);
|
|
if (sog != null)
|
|
{
|
|
mapitem = new mapItemReply(
|
|
xstart + (uint)sog.AbsolutePosition.X,
|
|
ystart + (uint)sog.AbsolutePosition.Y,
|
|
UUID.Zero,
|
|
sog.Name,
|
|
0, // color (not used)
|
|
0 // 0 = telehub / 1 = infohub
|
|
);
|
|
mapitems.Add(mapitem);
|
|
remoteClient.SendMapItemReply(mapitems.ToArray(), 1, flags);
|
|
mapitems.Clear();
|
|
}
|
|
|
|
// Service 7 (MAP_ITEM_LAND_FOR_SALE)
|
|
|
|
uint its = 7;
|
|
if (m_scene.RegionInfo.RegionSettings.Maturity == 2)
|
|
its = 10;
|
|
|
|
// Parcels
|
|
ILandChannel landChannel = m_scene.LandChannel;
|
|
List<ILandObject> parcels = landChannel.AllParcels();
|
|
|
|
if ((parcels != null) && (parcels.Count >= 1))
|
|
{
|
|
foreach (ILandObject parcel_interface in parcels)
|
|
{
|
|
// Play it safe
|
|
if (!(parcel_interface is LandObject))
|
|
continue;
|
|
|
|
LandObject land = (LandObject)parcel_interface;
|
|
LandData parcel = land.LandData;
|
|
|
|
// Show land for sale
|
|
if ((parcel.Flags & (uint)ParcelFlags.ForSale) == (uint)ParcelFlags.ForSale)
|
|
{
|
|
Vector3 min = parcel.AABBMin;
|
|
Vector3 max = parcel.AABBMax;
|
|
float x = (min.X + max.X) / 2;
|
|
float y = (min.Y + max.Y) / 2;
|
|
mapitem = new mapItemReply(
|
|
xstart + (uint)x,
|
|
ystart + (uint)y,
|
|
parcel.GlobalID,
|
|
parcel.Name,
|
|
parcel.Area,
|
|
parcel.SalePrice
|
|
);
|
|
mapitems.Add(mapitem);
|
|
}
|
|
}
|
|
if(mapitems.Count >0)
|
|
remoteClient.SendMapItemReply(mapitems.ToArray(), its, flags);
|
|
mapitems.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
private int nAsyncRequests = 0;
|
|
/// <summary>
|
|
/// Processing thread main() loop for doing remote mapitem requests
|
|
/// </summary>
|
|
public void process()
|
|
{
|
|
const int MAX_ASYNC_REQUESTS = 20;
|
|
ScenePresence av = null;
|
|
MapRequestState st = null;
|
|
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
av = null;
|
|
st = null;
|
|
|
|
requests.TryTake(out st, 4500);
|
|
Watchdog.UpdateThread();
|
|
|
|
if (st == null || st.agentID == UUID.Zero)
|
|
continue;
|
|
|
|
// end gracefully
|
|
if (st.agentID == STOP_UUID)
|
|
break;
|
|
|
|
// agent gone?
|
|
|
|
m_scene.TryGetScenePresence(st.agentID, out av);
|
|
if (av == null || av.IsChildAgent || av.IsDeleted || av.IsInTransit)
|
|
continue;
|
|
|
|
// region unreachable?
|
|
if (m_blacklistedregions.Contains(st.regionhandle))
|
|
continue;
|
|
|
|
bool dorequest = true;
|
|
OSDMap responseMap = null;
|
|
|
|
// check if we are already serving this region
|
|
lock (m_cachedRegionMapItemsResponses)
|
|
{
|
|
if (m_cachedRegionMapItemsResponses.Contains(st.regionhandle))
|
|
{
|
|
m_cachedRegionMapItemsResponses.TryGetValue(st.regionhandle, out responseMap);
|
|
dorequest = false;
|
|
}
|
|
else
|
|
m_cachedRegionMapItemsResponses.Add(st.regionhandle, null, expireResponsesTime); // a bit more time for the access
|
|
}
|
|
|
|
if (dorequest)
|
|
{
|
|
// nothig for region, fire a request
|
|
Interlocked.Increment(ref nAsyncRequests);
|
|
MapRequestState rst = st;
|
|
Util.FireAndForget(x =>
|
|
{
|
|
RequestMapItemsAsync(rst.agentID, rst.flags, rst.EstateID, rst.godlike, rst.itemtype, rst.regionhandle);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// do we have the response?
|
|
if (responseMap != null)
|
|
{
|
|
if(av!=null)
|
|
{
|
|
// this will mainly only send green dots now
|
|
if (responseMap.ContainsKey(st.itemtype.ToString()))
|
|
{
|
|
List<mapItemReply> returnitems = new List<mapItemReply>();
|
|
OSDArray itemarray = (OSDArray)responseMap[st.itemtype.ToString()];
|
|
for (int i = 0; i < itemarray.Count; i++)
|
|
{
|
|
OSDMap mapitem = (OSDMap)itemarray[i];
|
|
mapItemReply mi = new mapItemReply();
|
|
mi.x = (uint)mapitem["X"].AsInteger();
|
|
mi.y = (uint)mapitem["Y"].AsInteger();
|
|
mi.id = mapitem["ID"].AsUUID();
|
|
mi.Extra = mapitem["Extra"].AsInteger();
|
|
mi.Extra2 = mapitem["Extra2"].AsInteger();
|
|
mi.name = mapitem["Name"].AsString();
|
|
returnitems.Add(mi);
|
|
}
|
|
av.ControllingClient.SendMapItemReply(returnitems.ToArray(), st.itemtype, st.flags & 0xffff);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// request still beeing processed, enqueue it back
|
|
requests.Add(st);
|
|
if (requests.Count < 3)
|
|
Thread.Sleep(100);
|
|
}
|
|
}
|
|
|
|
while (nAsyncRequests >= MAX_ASYNC_REQUESTS) // hit the break
|
|
{
|
|
Thread.Sleep(100);
|
|
Watchdog.UpdateThread();
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("[WORLD MAP]: Map item request thread terminated abnormally with exception {0}", e);
|
|
}
|
|
|
|
threadrunning = false;
|
|
Watchdog.RemoveThread();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enqueue the MapItem request for remote processing
|
|
/// </summary>
|
|
/// <param name="httpserver">blank string, we discover this in the process</param>
|
|
/// <param name="id">Agent ID that we are making this request on behalf</param>
|
|
/// <param name="flags">passed in from packet</param>
|
|
/// <param name="EstateID">passed in from packet</param>
|
|
/// <param name="godlike">passed in from packet</param>
|
|
/// <param name="itemtype">passed in from packet</param>
|
|
/// <param name="regionhandle">Region we're looking up</param>
|
|
public void RequestMapItems(string httpserver, UUID id, uint flags,
|
|
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
|
{
|
|
MapRequestState st = new MapRequestState();
|
|
st.agentID = id;
|
|
st.flags = flags;
|
|
st.EstateID = EstateID;
|
|
st.godlike = godlike;
|
|
st.itemtype = itemtype;
|
|
st.regionhandle = regionhandle;
|
|
|
|
requests.Add(st);
|
|
}
|
|
|
|
uint[] itemTypesForcedSend = new uint[] { 6, 1, 7, 10 }; // green dots, infohub, land sells
|
|
|
|
/// <summary>
|
|
/// Does the actual remote mapitem request
|
|
/// This should be called from an asynchronous thread
|
|
/// Request failures get blacklisted until region restart so we don't
|
|
/// continue to spend resources trying to contact regions that are down.
|
|
/// </summary>
|
|
/// <param name="httpserver">blank string, we discover this in the process</param>
|
|
/// <param name="id">Agent ID that we are making this request on behalf</param>
|
|
/// <param name="flags">passed in from packet</param>
|
|
/// <param name="EstateID">passed in from packet</param>
|
|
/// <param name="godlike">passed in from packet</param>
|
|
/// <param name="itemtype">passed in from packet</param>
|
|
/// <param name="regionhandle">Region we're looking up</param>
|
|
/// <returns></returns>
|
|
private void RequestMapItemsAsync(UUID id, uint flags,
|
|
uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
|
|
{
|
|
// m_log.DebugFormat("[WORLDMAP]: RequestMapItemsAsync; region handle: {0} {1}", regionhandle, itemtype);
|
|
|
|
string httpserver = "";
|
|
bool blacklisted = false;
|
|
|
|
lock (m_blacklistedregions)
|
|
blacklisted = m_blacklistedregions.Contains(regionhandle);
|
|
|
|
if (blacklisted)
|
|
{
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
|
|
UUID requestID = UUID.Random();
|
|
lock (m_cachedRegionMapItemsAddress)
|
|
m_cachedRegionMapItemsAddress.TryGetValue(regionhandle, out httpserver);
|
|
|
|
if (httpserver == null || httpserver.Length == 0)
|
|
{
|
|
uint x = 0, y = 0;
|
|
Util.RegionHandleToWorldLoc(regionhandle, out x, out y);
|
|
|
|
GridRegion mreg = m_scene.GridService.GetRegionByPosition(m_scene.RegionInfo.ScopeID, (int)x, (int)y);
|
|
|
|
if (mreg != null)
|
|
{
|
|
httpserver = mreg.ServerURI + "MAP/MapItems/" + regionhandle.ToString();
|
|
lock (m_cachedRegionMapItemsAddress)
|
|
m_cachedRegionMapItemsAddress.AddOrUpdate(regionhandle, httpserver, 2.0 * expireBlackListTime);
|
|
}
|
|
}
|
|
|
|
lock (m_blacklistedurls)
|
|
{
|
|
if (httpserver == null || httpserver.Length == 0 || m_blacklistedurls.Contains(httpserver))
|
|
{
|
|
// Can't find the http server or its blocked
|
|
lock (m_blacklistedregions)
|
|
m_blacklistedregions.AddOrUpdate(regionhandle, 0, expireBlackListTime);
|
|
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
}
|
|
|
|
WebRequest mapitemsrequest = null;
|
|
try
|
|
{
|
|
mapitemsrequest = WebRequest.Create(httpserver);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.DebugFormat("[WORLD MAP]: Access to {0} failed with {1}", httpserver, e);
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
|
|
mapitemsrequest.Method = "POST";
|
|
mapitemsrequest.ContentType = "application/xml+llsd";
|
|
|
|
OSDMap RAMap = new OSDMap();
|
|
// string RAMapString = RAMap.ToString();
|
|
OSD LLSDofRAMap = RAMap; // RENAME if this works
|
|
|
|
byte[] buffer = OSDParser.SerializeLLSDXmlBytes(LLSDofRAMap);
|
|
|
|
OSDMap responseMap = new OSDMap();
|
|
|
|
try
|
|
{ // send the Post
|
|
mapitemsrequest.ContentLength = buffer.Length; //Count bytes to send
|
|
using (Stream os = mapitemsrequest.GetRequestStream())
|
|
os.Write(buffer, 0, buffer.Length); //Send it
|
|
//m_log.DebugFormat("[WORLD MAP]: Getting MapItems from {0}", httpserver);
|
|
}
|
|
catch (WebException ex)
|
|
{
|
|
m_log.WarnFormat("[WORLD MAP]: Bad send on GetMapItems {0}", ex.Message);
|
|
m_log.WarnFormat("[WORLD MAP]: Blacklisted url {0}", httpserver);
|
|
lock (m_blacklistedurls)
|
|
m_blacklistedurls.AddOrUpdate(httpserver, 0, expireBlackListTime);
|
|
lock (m_blacklistedregions)
|
|
m_blacklistedregions.AddOrUpdate(regionhandle, 0, expireBlackListTime);
|
|
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
catch
|
|
{
|
|
m_log.DebugFormat("[WORLD MAP]: RequestMapItems failed for {0}", httpserver);
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
|
|
string response_mapItems_reply = null;
|
|
{ // get the response
|
|
try
|
|
{
|
|
using (WebResponse webResponse = mapitemsrequest.GetResponse())
|
|
{
|
|
if (webResponse != null)
|
|
{
|
|
using (StreamReader sr = new StreamReader(webResponse.GetResponseStream()))
|
|
{
|
|
response_mapItems_reply = sr.ReadToEnd().Trim();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch (WebException)
|
|
{
|
|
lock (m_blacklistedurls)
|
|
m_blacklistedurls.AddOrUpdate(httpserver, 0, expireBlackListTime);
|
|
lock (m_blacklistedregions)
|
|
m_blacklistedregions.AddOrUpdate(regionhandle, 0, expireBlackListTime);
|
|
|
|
m_log.WarnFormat("[WORLD MAP]: Blacklisted url {0}", httpserver);
|
|
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
catch
|
|
{
|
|
m_log.DebugFormat("[WORLD MAP]: RequestMapItems failed for {0}", httpserver);
|
|
lock (m_blacklistedregions)
|
|
m_blacklistedregions.AddOrUpdate(regionhandle, 0, expireBlackListTime);
|
|
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
responseMap = (OSDMap)OSDParser.DeserializeLLSDXml(response_mapItems_reply);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.InfoFormat("[WORLD MAP]: exception on parse of RequestMapItems reply from {0}: {1}", httpserver, ex.Message);
|
|
lock (m_blacklistedregions)
|
|
m_blacklistedregions.AddOrUpdate(regionhandle, 0, expireBlackListTime);
|
|
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// cache the response that may include other valid items
|
|
lock (m_cachedRegionMapItemsResponses)
|
|
m_cachedRegionMapItemsResponses.AddOrUpdate(regionhandle, responseMap, expireResponsesTime);
|
|
|
|
flags &= 0xffff;
|
|
|
|
if (id != UUID.Zero)
|
|
{
|
|
ScenePresence av = null;
|
|
m_scene.TryGetScenePresence(id, out av);
|
|
if (av != null && !av.IsChildAgent && !av.IsDeleted && !av.IsInTransit)
|
|
{
|
|
// send all the items or viewers will never ask for them, except green dots
|
|
foreach (uint itfs in itemTypesForcedSend)
|
|
{
|
|
if (responseMap.ContainsKey(itfs.ToString()))
|
|
{
|
|
List<mapItemReply> returnitems = new List<mapItemReply>();
|
|
// OSDArray itemarray = (OSDArray)responseMap[itemtype.ToString()];
|
|
OSDArray itemarray = (OSDArray)responseMap[itfs.ToString()];
|
|
for (int i = 0; i < itemarray.Count; i++)
|
|
{
|
|
OSDMap mapitem = (OSDMap)itemarray[i];
|
|
mapItemReply mi = new mapItemReply();
|
|
mi.x = (uint)mapitem["X"].AsInteger();
|
|
mi.y = (uint)mapitem["Y"].AsInteger();
|
|
mi.id = mapitem["ID"].AsUUID();
|
|
mi.Extra = mapitem["Extra"].AsInteger();
|
|
mi.Extra2 = mapitem["Extra2"].AsInteger();
|
|
mi.name = mapitem["Name"].AsString();
|
|
returnitems.Add(mi);
|
|
}
|
|
// av.ControllingClient.SendMapItemReply(returnitems.ToArray(), itemtype, flags);
|
|
av.ControllingClient.SendMapItemReply(returnitems.ToArray(), itfs, flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Interlocked.Decrement(ref nAsyncRequests);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests map blocks in area of minX, maxX, minY, MaxY in world cordinates
|
|
/// </summary>
|
|
/// <param name="minX"></param>
|
|
/// <param name="minY"></param>
|
|
/// <param name="maxX"></param>
|
|
/// <param name="maxY"></param>
|
|
public void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag)
|
|
{
|
|
// m_log.DebugFormat("[WoldMapModule] RequestMapBlocks {0}={1}={2}={3} {4}", minX, minY, maxX, maxY, flag);
|
|
|
|
GetAndSendBlocks(remoteClient, minX, minY, maxX, maxY, flag);
|
|
}
|
|
|
|
private const double SPAMBLOCKTIMEms = 300000; // 5 minutes
|
|
private Dictionary<UUID,double> spamBlocked = new Dictionary<UUID,double>();
|
|
|
|
protected virtual List<MapBlockData> GetAndSendBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag)
|
|
{
|
|
// anti spam because of FireStorm 4.7.7 absurd request repeat rates
|
|
// possible others
|
|
|
|
double now = Util.GetTimeStampMS();
|
|
UUID agentID = remoteClient.AgentId;
|
|
|
|
lock (m_mapBlockRequestEvent)
|
|
{
|
|
if(spamBlocked.ContainsKey(agentID))
|
|
{
|
|
if(spamBlocked[agentID] < now &&
|
|
(!m_mapBlockRequests.ContainsKey(agentID) ||
|
|
m_mapBlockRequests[agentID].Count == 0 ))
|
|
{
|
|
spamBlocked.Remove(agentID);
|
|
m_log.DebugFormat("[WoldMapModule] RequestMapBlocks release spammer {0}", agentID);
|
|
}
|
|
else
|
|
return new List<MapBlockData>();
|
|
}
|
|
else
|
|
{
|
|
// ugly slow expire spammers
|
|
if(spamBlocked.Count > 0)
|
|
{
|
|
UUID k = UUID.Zero;
|
|
bool expireone = false;
|
|
foreach(UUID k2 in spamBlocked.Keys)
|
|
{
|
|
if(spamBlocked[k2] < now &&
|
|
(!m_mapBlockRequests.ContainsKey(k2) ||
|
|
m_mapBlockRequests[k2].Count == 0 ))
|
|
{
|
|
m_log.DebugFormat("[WoldMapModule] RequestMapBlocks release spammer {0}", k2);
|
|
k = k2;
|
|
expireone = true;
|
|
}
|
|
break; // doing one at a time
|
|
}
|
|
if(expireone)
|
|
spamBlocked.Remove(k);
|
|
}
|
|
}
|
|
|
|
// m_log.DebugFormat("[WoldMapModule] RequestMapBlocks {0}={1}={2}={3} {4}", minX, minY, maxX, maxY, flag);
|
|
|
|
MapBlockRequestData req = new MapBlockRequestData();
|
|
|
|
req.client = remoteClient;
|
|
req.minX = minX;
|
|
req.maxX = maxX;
|
|
req.minY = minY;
|
|
req.maxY = maxY;
|
|
req.flags = flag;
|
|
|
|
if (!m_mapBlockRequests.ContainsKey(agentID))
|
|
m_mapBlockRequests[agentID] = new Queue<MapBlockRequestData>();
|
|
if(m_mapBlockRequests[agentID].Count < 150 )
|
|
m_mapBlockRequests[agentID].Enqueue(req);
|
|
else
|
|
{
|
|
spamBlocked[agentID] = now + SPAMBLOCKTIMEms;
|
|
m_log.DebugFormat("[WoldMapModule] RequestMapBlocks blocking spammer {0} for {1} s",agentID, SPAMBLOCKTIMEms/1000.0);
|
|
}
|
|
m_mapBlockRequestEvent.Set();
|
|
}
|
|
|
|
return new List<MapBlockData>();
|
|
}
|
|
|
|
protected void MapBlockSendThread()
|
|
{
|
|
List<MapBlockRequestData> thisRunData = new List<MapBlockRequestData>();
|
|
while (true)
|
|
{
|
|
while(!m_mapBlockRequestEvent.WaitOne(4900))
|
|
{
|
|
Watchdog.UpdateThread();
|
|
if(m_scene == null)
|
|
return;
|
|
}
|
|
Watchdog.UpdateThread();
|
|
lock (m_mapBlockRequestEvent)
|
|
{
|
|
int total = 0;
|
|
foreach (Queue<MapBlockRequestData> q in m_mapBlockRequests.Values)
|
|
{
|
|
if (q.Count > 0)
|
|
thisRunData.Add(q.Dequeue());
|
|
|
|
total += q.Count;
|
|
}
|
|
|
|
if (total == 0)
|
|
m_mapBlockRequestEvent.Reset();
|
|
}
|
|
|
|
if(thisRunData.Count > 0)
|
|
{
|
|
foreach (MapBlockRequestData req in thisRunData)
|
|
{
|
|
// Null client stops thread
|
|
if (req.client == null)
|
|
return;
|
|
|
|
GetAndSendBlocksInternal(req.client, req.minX, req.minY, req.maxX, req.maxY, req.flags);
|
|
Watchdog.UpdateThread();
|
|
}
|
|
|
|
thisRunData.Clear();
|
|
}
|
|
|
|
Thread.Sleep(50);
|
|
}
|
|
}
|
|
|
|
protected virtual List<MapBlockData> GetAndSendBlocksInternal(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag)
|
|
{
|
|
List<MapBlockData> allBlocks = new List<MapBlockData>();
|
|
List<MapBlockData> mapBlocks = new List<MapBlockData>();
|
|
List<GridRegion> regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID,
|
|
minX * (int)Constants.RegionSize,
|
|
maxX * (int)Constants.RegionSize,
|
|
minY * (int)Constants.RegionSize,
|
|
maxY * (int)Constants.RegionSize);
|
|
|
|
// only send a negative answer for a single region request
|
|
// corresponding to a click on the map. Current viewers
|
|
// keep displaying "loading.." without this
|
|
if (regions.Count == 0 && (flag & 0x10000) != 0 && minX == maxX && minY == maxY)
|
|
{
|
|
MapBlockData block = new MapBlockData();
|
|
block.X = (ushort)minX;
|
|
block.Y = (ushort)minY;
|
|
block.MapImageId = UUID.Zero;
|
|
block.Access = (byte)SimAccess.NonExistent;
|
|
allBlocks.Add(block);
|
|
mapBlocks.Add(block);
|
|
remoteClient.SendMapBlock(mapBlocks, flag & 0xffff);
|
|
return allBlocks;
|
|
}
|
|
|
|
flag &= 0xffff;
|
|
|
|
foreach (GridRegion r in regions)
|
|
{
|
|
if (r == null)
|
|
continue;
|
|
MapBlockData block = new MapBlockData();
|
|
MapBlockFromGridRegion(block, r, flag);
|
|
mapBlocks.Add(block);
|
|
allBlocks.Add(block);
|
|
|
|
if (mapBlocks.Count >= 10)
|
|
{
|
|
remoteClient.SendMapBlock(mapBlocks, flag);
|
|
mapBlocks.Clear();
|
|
Thread.Sleep(50);
|
|
}
|
|
}
|
|
if (mapBlocks.Count > 0)
|
|
remoteClient.SendMapBlock(mapBlocks, flag);
|
|
|
|
return allBlocks;
|
|
}
|
|
|
|
public void MapBlockFromGridRegion(MapBlockData block, GridRegion r, uint flag)
|
|
{
|
|
if (r == null)
|
|
{
|
|
// we should not get here ??
|
|
// block.Access = (byte)SimAccess.Down; this is for a grid reply on r
|
|
block.Access = (byte)SimAccess.NonExistent;
|
|
block.MapImageId = UUID.Zero;
|
|
return;
|
|
}
|
|
|
|
block.Access = r.Access;
|
|
switch (flag)
|
|
{
|
|
case 0:
|
|
block.MapImageId = r.TerrainImage;
|
|
break;
|
|
case 2:
|
|
block.MapImageId = r.ParcelImage;
|
|
break;
|
|
default:
|
|
block.MapImageId = UUID.Zero;
|
|
break;
|
|
}
|
|
block.Name = r.RegionName;
|
|
block.X = (ushort)(r.RegionLocX / Constants.RegionSize);
|
|
block.Y = (ushort)(r.RegionLocY / Constants.RegionSize);
|
|
block.SizeX = (ushort)r.RegionSizeX;
|
|
block.SizeY = (ushort)r.RegionSizeY;
|
|
|
|
}
|
|
|
|
public Hashtable OnHTTPThrottled(Hashtable keysvals)
|
|
{
|
|
Hashtable reply = new Hashtable();
|
|
int statuscode = 500;
|
|
reply["str_response_string"] = "";
|
|
reply["int_response_code"] = statuscode;
|
|
reply["content_type"] = "text/plain";
|
|
return reply;
|
|
}
|
|
|
|
public void OnHTTPGetMapImage(IOSHttpRequest request, IOSHttpResponse response)
|
|
{
|
|
response.KeepAlive = false;
|
|
if (request.HttpMethod != "GET" || m_scene.RegionInfo.RegionSettings.TerrainImageID == UUID.Zero)
|
|
{
|
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
|
return;
|
|
}
|
|
|
|
byte[] jpeg = null;
|
|
m_log.Debug("[WORLD MAP]: Sending map image jpeg");
|
|
|
|
if (myMapImageJPEG.Length == 0)
|
|
{
|
|
MemoryStream imgstream = null;
|
|
Bitmap mapTexture = new Bitmap(1, 1);
|
|
ManagedImage managedImage;
|
|
Image image = (Image)mapTexture;
|
|
|
|
try
|
|
{
|
|
// Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular jpeg data
|
|
|
|
imgstream = new MemoryStream();
|
|
|
|
// non-async because we know we have the asset immediately.
|
|
AssetBase mapasset = m_scene.AssetService.Get(m_scene.RegionInfo.RegionSettings.TerrainImageID.ToString());
|
|
if(mapasset == null || mapasset.Data == null || mapasset.Data.Length == 0)
|
|
{
|
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
|
return;
|
|
}
|
|
|
|
// Decode image to System.Drawing.Image
|
|
if (OpenJPEG.DecodeToImage(mapasset.Data, out managedImage, out image))
|
|
{
|
|
// Save to bitmap
|
|
mapTexture = new Bitmap(image);
|
|
|
|
EncoderParameters myEncoderParameters = new EncoderParameters();
|
|
myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L);
|
|
|
|
// Save bitmap to stream
|
|
mapTexture.Save(imgstream, GetEncoderInfo("image/jpeg"), myEncoderParameters);
|
|
|
|
// Write the stream to a byte array for output
|
|
jpeg = imgstream.ToArray();
|
|
myMapImageJPEG = jpeg;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Dummy!
|
|
m_log.Warn("[WORLD MAP]: Unable to generate Map image" + e.Message);
|
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
// Reclaim memory, these are unmanaged resources
|
|
// If we encountered an exception, one or more of these will be null
|
|
if (mapTexture != null)
|
|
mapTexture.Dispose();
|
|
|
|
if (image != null)
|
|
image.Dispose();
|
|
|
|
if (imgstream != null)
|
|
imgstream.Dispose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Use cached version so we don't have to loose our mind
|
|
jpeg = myMapImageJPEG;
|
|
}
|
|
if(jpeg == null)
|
|
{
|
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
|
return;
|
|
}
|
|
|
|
response.RawBuffer = jpeg;
|
|
//response.RawBuffer = Convert.ToBase64String(jpeg);
|
|
response.ContentType = "image/jpeg";
|
|
response.StatusCode = (int)HttpStatusCode.OK;
|
|
}
|
|
|
|
// From msdn
|
|
private static ImageCodecInfo GetEncoderInfo(String mimeType)
|
|
{
|
|
ImageCodecInfo[] encoders;
|
|
encoders = ImageCodecInfo.GetImageEncoders();
|
|
for (int j = 0; j < encoders.Length; ++j)
|
|
{
|
|
if (encoders[j].MimeType == mimeType)
|
|
return encoders[j];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Export the world map
|
|
/// </summary>
|
|
/// <param name="fileName"></param>
|
|
public void HandleExportWorldMapConsoleCommand(string module, string[] cmdparams)
|
|
{
|
|
if (m_scene.ConsoleScene() == null)
|
|
{
|
|
// FIXME: If console region is root then this will be printed by every module. Currently, there is no
|
|
// way to prevent this, short of making the entire module shared (which is complete overkill).
|
|
// One possibility is to return a bool to signal whether the module has completely handled the command
|
|
m_log.InfoFormat("[WORLD MAP]: Please change to a specific region in order to export its world map");
|
|
return;
|
|
}
|
|
|
|
if (m_scene.ConsoleScene() != m_scene)
|
|
return;
|
|
|
|
string exportPath;
|
|
|
|
if (cmdparams.Length > 1)
|
|
exportPath = cmdparams[1];
|
|
else
|
|
exportPath = DEFAULT_WORLD_MAP_EXPORT_PATH;
|
|
|
|
m_log.InfoFormat(
|
|
"[WORLD MAP]: Exporting world map for {0} to {1}", m_scene.RegionInfo.RegionName, exportPath);
|
|
|
|
// assumed this is 1m less than next grid line
|
|
int regionsView = (int)m_scene.MaxRegionViewDistance;
|
|
|
|
string regionName = m_scene.RegionInfo.RegionName;
|
|
ulong regionHandle = m_scene.RegionInfo.RegionHandle;
|
|
|
|
int regionSizeX = (int)m_scene.RegionInfo.RegionSizeX;
|
|
int regionSizeY = (int)m_scene.RegionInfo.RegionSizeY;
|
|
|
|
int regionX = (int)m_scene.RegionInfo.WorldLocX;
|
|
int regionY = (int)m_scene.RegionInfo.WorldLocY;
|
|
|
|
int startX = regionX - regionsView;
|
|
int startY = regionY - regionsView;
|
|
|
|
int endX = regionX + regionSizeX + regionsView;
|
|
int endY = regionY + regionSizeY + regionsView;
|
|
|
|
int spanX = endX - startX + 2;
|
|
int spanY = endY - startY + 2;
|
|
|
|
Bitmap mapTexture = new Bitmap(spanX, spanY);
|
|
ImageAttributes gatrib = new ImageAttributes();
|
|
gatrib.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);
|
|
|
|
Graphics g = Graphics.FromImage(mapTexture);
|
|
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
|
|
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
|
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
|
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
|
|
|
|
SolidBrush sea = new SolidBrush(Color.DarkBlue);
|
|
g.FillRectangle(sea, 0, 0, spanX, spanY);
|
|
sea.Dispose();
|
|
|
|
Font drawFont = new Font("Arial", 32);
|
|
SolidBrush drawBrush = new SolidBrush(Color.White);
|
|
|
|
List<GridRegion> regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID,
|
|
startX, startY, endX, endY);
|
|
|
|
startX--;
|
|
startY--;
|
|
|
|
bool doneLocal = false;
|
|
string filename = "MAP-" + m_scene.RegionInfo.RegionID.ToString() + ".png";
|
|
try
|
|
{
|
|
using(Image localMap = Bitmap.FromFile(filename))
|
|
{
|
|
int x = regionX - startX;
|
|
int y = regionY - startY;
|
|
int sx = regionSizeX;
|
|
int sy = regionSizeY;
|
|
// y origin is top
|
|
g.DrawImage(localMap,new Rectangle(x, spanY - y - sy, sx, sy),
|
|
0, 0, localMap.Width, localMap.Height, GraphicsUnit.Pixel, gatrib);
|
|
|
|
if(m_exportPrintRegionName)
|
|
{
|
|
SizeF stringSize = g.MeasureString(regionName, drawFont);
|
|
g.DrawString(regionName, drawFont, drawBrush, x + 30, spanY - y - 30 - stringSize.Height);
|
|
}
|
|
}
|
|
doneLocal = true;
|
|
}
|
|
catch {}
|
|
|
|
if(regions.Count > 0)
|
|
{
|
|
ManagedImage managedImage = null;
|
|
Image image = null;
|
|
|
|
foreach(GridRegion r in regions)
|
|
{
|
|
if(r.TerrainImage == UUID.Zero)
|
|
continue;
|
|
|
|
if(doneLocal && r.RegionHandle == regionHandle)
|
|
continue;
|
|
|
|
AssetBase texAsset = m_scene.AssetService.Get(r.TerrainImage.ToString());
|
|
if(texAsset == null)
|
|
continue;
|
|
|
|
if(OpenJPEG.DecodeToImage(texAsset.Data, out managedImage, out image))
|
|
{
|
|
int x = r.RegionLocX - startX;
|
|
int y = r.RegionLocY - startY;
|
|
int sx = r.RegionSizeX;
|
|
int sy = r.RegionSizeY;
|
|
// y origin is top
|
|
g.DrawImage(image,new Rectangle(x, spanY - y - sy, sx, sy),
|
|
0, 0, image.Width, image.Height, GraphicsUnit.Pixel, gatrib);
|
|
|
|
if(m_exportPrintRegionName && r.RegionHandle == regionHandle)
|
|
{
|
|
SizeF stringSize = g.MeasureString(r.RegionName, drawFont);
|
|
g.DrawString(r.RegionName, drawFont, drawBrush, x + 30, spanY - y - 30 - stringSize.Height);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(image != null)
|
|
image.Dispose();
|
|
|
|
}
|
|
|
|
if(m_exportPrintScale)
|
|
{
|
|
String drawString = string.Format("{0}m x {1}m", spanX, spanY);
|
|
g.DrawString(drawString, drawFont, drawBrush, 30, 30);
|
|
}
|
|
|
|
drawBrush.Dispose();
|
|
drawFont.Dispose();
|
|
gatrib.Dispose();
|
|
g.Dispose();
|
|
|
|
mapTexture.Save(exportPath, ImageFormat.Jpeg);
|
|
mapTexture.Dispose();
|
|
|
|
m_log.InfoFormat(
|
|
"[WORLD MAP]: Successfully exported world map for {0} to {1}",
|
|
m_scene.RegionInfo.RegionName, exportPath);
|
|
}
|
|
|
|
public void HandleGenerateMapConsoleCommand(string module, string[] cmdparams)
|
|
{
|
|
Scene consoleScene = m_scene.ConsoleScene();
|
|
|
|
if (consoleScene != null && consoleScene != m_scene)
|
|
return;
|
|
|
|
m_scene.RegenerateMaptileAndReregister(this, null);
|
|
}
|
|
|
|
public void HandleRemoteMapItemRequest(IOSHttpRequest request, IOSHttpResponse response)
|
|
{
|
|
uint xstart = 0;
|
|
uint ystart = 0;
|
|
|
|
Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle, out xstart, out ystart);
|
|
|
|
// Service 6 (MAP_ITEM_AGENTS_LOCATION; green dots)
|
|
|
|
OSDMap responsemap = new OSDMap();
|
|
int tc = Environment.TickCount;
|
|
if (m_scene.GetRootAgentCount() == 0)
|
|
{
|
|
OSDMap responsemapdata = new OSDMap();
|
|
responsemapdata["X"] = OSD.FromInteger((int)(xstart + 1));
|
|
responsemapdata["Y"] = OSD.FromInteger((int)(ystart + 1));
|
|
responsemapdata["ID"] = OSD.FromUUID(UUID.Zero);
|
|
responsemapdata["Name"] = OSD.FromString(Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()));
|
|
responsemapdata["Extra"] = OSD.FromInteger(0);
|
|
responsemapdata["Extra2"] = OSD.FromInteger(0);
|
|
OSDArray responsearr = new OSDArray();
|
|
responsearr.Add(responsemapdata);
|
|
|
|
responsemap["6"] = responsearr;
|
|
}
|
|
else
|
|
{
|
|
OSDArray responsearr = new OSDArray(); // Don't preallocate. MT (m_scene.GetRootAgentCount());
|
|
m_scene.ForEachRootScenePresence(delegate (ScenePresence sp)
|
|
{
|
|
if (!m_showNPCs && sp.IsNPC)
|
|
return;
|
|
OSDMap responsemapdata = new OSDMap();
|
|
responsemapdata["X"] = OSD.FromInteger((int)(xstart + sp.AbsolutePosition.X));
|
|
responsemapdata["Y"] = OSD.FromInteger((int)(ystart + sp.AbsolutePosition.Y));
|
|
responsemapdata["ID"] = OSD.FromUUID(UUID.Zero);
|
|
responsemapdata["Name"] = OSD.FromString(Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()));
|
|
responsemapdata["Extra"] = OSD.FromInteger(1);
|
|
responsemapdata["Extra2"] = OSD.FromInteger(0);
|
|
responsearr.Add(responsemapdata);
|
|
});
|
|
responsemap["6"] = responsearr;
|
|
}
|
|
|
|
// Service 7/10 (MAP_ITEM_LAND_FOR_SALE/ADULT)
|
|
|
|
ILandChannel landChannel = m_scene.LandChannel;
|
|
List<ILandObject> parcels = landChannel.AllParcels();
|
|
|
|
if ((parcels != null) && (parcels.Count >= 0))
|
|
{
|
|
OSDArray responsearr = new OSDArray(parcels.Count);
|
|
foreach (ILandObject parcel_interface in parcels)
|
|
{
|
|
// Play it safe
|
|
if (!(parcel_interface is LandObject))
|
|
continue;
|
|
|
|
LandObject land = (LandObject)parcel_interface;
|
|
LandData parcel = land.LandData;
|
|
|
|
// Show land for sale
|
|
if ((parcel.Flags & (uint)ParcelFlags.ForSale) == (uint)ParcelFlags.ForSale)
|
|
{
|
|
Vector3 min = parcel.AABBMin;
|
|
Vector3 max = parcel.AABBMax;
|
|
float x = (min.X + max.X) / 2;
|
|
float y = (min.Y + max.Y) / 2;
|
|
|
|
OSDMap responsemapdata = new OSDMap();
|
|
responsemapdata["X"] = OSD.FromInteger((int)(xstart + x));
|
|
responsemapdata["Y"] = OSD.FromInteger((int)(ystart + y));
|
|
// responsemapdata["Z"] = OSD.FromInteger((int)m_scene.GetGroundHeight(x,y));
|
|
responsemapdata["ID"] = OSD.FromUUID(parcel.GlobalID);
|
|
responsemapdata["Name"] = OSD.FromString(parcel.Name);
|
|
responsemapdata["Extra"] = OSD.FromInteger(parcel.Area);
|
|
responsemapdata["Extra2"] = OSD.FromInteger(parcel.SalePrice);
|
|
responsearr.Add(responsemapdata);
|
|
}
|
|
}
|
|
|
|
if(responsearr.Count > 0)
|
|
{
|
|
if(m_scene.RegionInfo.RegionSettings.Maturity == 2)
|
|
responsemap["10"] = responsearr;
|
|
else
|
|
responsemap["7"] = responsearr;
|
|
}
|
|
}
|
|
|
|
if (m_scene.RegionInfo.RegionSettings.TelehubObject != UUID.Zero)
|
|
{
|
|
SceneObjectGroup sog = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject);
|
|
if (sog != null)
|
|
{
|
|
OSDArray responsearr = new OSDArray();
|
|
OSDMap responsemapdata = new OSDMap();
|
|
responsemapdata["X"] = OSD.FromInteger((int)(xstart + sog.AbsolutePosition.X));
|
|
responsemapdata["Y"] = OSD.FromInteger((int)(ystart + sog.AbsolutePosition.Y));
|
|
// responsemapdata["Z"] = OSD.FromInteger((int)m_scene.GetGroundHeight(x,y));
|
|
responsemapdata["ID"] = OSD.FromUUID(sog.UUID);
|
|
responsemapdata["Name"] = OSD.FromString(sog.Name);
|
|
responsemapdata["Extra"] = OSD.FromInteger(0); // color (unused)
|
|
responsemapdata["Extra2"] = OSD.FromInteger(0); // 0 = telehub / 1 = infohub
|
|
responsearr.Add(responsemapdata);
|
|
|
|
responsemap["1"] = responsearr;
|
|
}
|
|
}
|
|
|
|
response.RawBuffer = OSDParser.SerializeLLSDXmlBytes(responsemap);
|
|
response.StatusCode = (int)HttpStatusCode.OK;
|
|
}
|
|
|
|
public void GenerateMaptile()
|
|
{
|
|
// Cannot create a map for a nonexistent heightmap
|
|
if (m_scene.Heightmap == null)
|
|
return;
|
|
|
|
if (m_mapImageGenerator == null)
|
|
{
|
|
Console.WriteLine("No map image generator available for {0}", m_scene.Name);
|
|
return;
|
|
}
|
|
m_log.DebugFormat("[WORLD MAP]: Generating map image for {0}", m_scene.Name);
|
|
|
|
using (Bitmap mapbmp = m_mapImageGenerator.CreateMapTile())
|
|
{
|
|
GenerateMaptile(mapbmp);
|
|
|
|
if (m_mapImageServiceModule != null)
|
|
m_mapImageServiceModule.UploadMapTile(m_scene, mapbmp);
|
|
}
|
|
}
|
|
|
|
private void GenerateMaptile(Bitmap mapbmp)
|
|
{
|
|
bool needRegionSave = false;
|
|
|
|
// remove old assets
|
|
UUID lastID = m_scene.RegionInfo.RegionSettings.TerrainImageID;
|
|
if (lastID != UUID.Zero)
|
|
{
|
|
m_scene.AssetService.Delete(lastID.ToString());
|
|
m_scene.RegionInfo.RegionSettings.TerrainImageID = UUID.Zero;
|
|
myMapImageJPEG = new byte[0];
|
|
needRegionSave = true;
|
|
}
|
|
|
|
lastID = m_scene.RegionInfo.RegionSettings.ParcelImageID;
|
|
if (lastID != UUID.Zero)
|
|
{
|
|
m_scene.AssetService.Delete(lastID.ToString());
|
|
m_scene.RegionInfo.RegionSettings.ParcelImageID = UUID.Zero;
|
|
needRegionSave = true;
|
|
}
|
|
|
|
if(mapbmp != null)
|
|
{
|
|
try
|
|
{
|
|
byte[] data;
|
|
|
|
// if large region limit its size since new viewers will not use it
|
|
// but it is still usable for ossl
|
|
if(m_scene.RegionInfo.RegionSizeX > Constants.RegionSize ||
|
|
m_scene.RegionInfo.RegionSizeY > Constants.RegionSize)
|
|
{
|
|
int bx = mapbmp.Width;
|
|
int by = mapbmp.Height;
|
|
int mb = bx;
|
|
if(mb < by)
|
|
mb = by;
|
|
if(mb > Constants.RegionSize && mb > 0)
|
|
{
|
|
float scale = (float)Constants.RegionSize/(float)mb;
|
|
using(Bitmap scaledbmp = Util.ResizeImageSolid(mapbmp, (int)(bx * scale), (int)(by * scale)))
|
|
data = OpenJPEG.EncodeFromImage(scaledbmp, true);
|
|
}
|
|
else
|
|
data = OpenJPEG.EncodeFromImage(mapbmp, true);
|
|
}
|
|
else
|
|
data = OpenJPEG.EncodeFromImage(mapbmp, true);
|
|
|
|
if (data != null && data.Length > 0)
|
|
{
|
|
UUID terrainImageID = UUID.Random();
|
|
|
|
AssetBase asset = new AssetBase(
|
|
terrainImageID,
|
|
"terrainImage_" + m_scene.RegionInfo.RegionID.ToString(),
|
|
(sbyte)AssetType.Texture,
|
|
m_scene.RegionInfo.RegionID.ToString());
|
|
asset.Data = data;
|
|
asset.Description = m_scene.RegionInfo.RegionName;
|
|
asset.Temporary = false;
|
|
asset.Flags = AssetFlags.Maptile;
|
|
|
|
// Store the new one
|
|
m_log.DebugFormat("[WORLD MAP]: Storing map image {0} for {1}", asset.ID, m_scene.RegionInfo.RegionName);
|
|
|
|
m_scene.AssetService.Store(asset);
|
|
|
|
m_scene.RegionInfo.RegionSettings.TerrainImageID = terrainImageID;
|
|
needRegionSave = true;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.Error("[WORLD MAP]: Failed generating terrain map: " + e);
|
|
}
|
|
}
|
|
|
|
// V2/3 still seem to need this, or we are missing something somewhere
|
|
byte[] overlay = GenerateOverlay();
|
|
if (overlay != null)
|
|
{
|
|
UUID parcelImageID = UUID.Random();
|
|
|
|
AssetBase parcels = new AssetBase(
|
|
parcelImageID,
|
|
"parcelImage_" + m_scene.RegionInfo.RegionID.ToString(),
|
|
(sbyte)AssetType.Texture,
|
|
m_scene.RegionInfo.RegionID.ToString());
|
|
parcels.Data = overlay;
|
|
parcels.Description = m_scene.RegionInfo.RegionName;
|
|
parcels.Temporary = false;
|
|
parcels.Flags = AssetFlags.Maptile;
|
|
|
|
m_scene.AssetService.Store(parcels);
|
|
|
|
m_scene.RegionInfo.RegionSettings.ParcelImageID = parcelImageID;
|
|
needRegionSave = true;
|
|
}
|
|
|
|
if (needRegionSave)
|
|
m_scene.RegionInfo.RegionSettings.Save();
|
|
}
|
|
|
|
private void MakeRootAgent(ScenePresence avatar)
|
|
{
|
|
lock (m_rootAgents)
|
|
{
|
|
if (!m_rootAgents.Contains(avatar.UUID))
|
|
{
|
|
m_rootAgents.Add(avatar.UUID);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MakeChildAgent(ScenePresence avatar)
|
|
{
|
|
lock (m_rootAgents)
|
|
{
|
|
m_rootAgents.Remove(avatar.UUID);
|
|
}
|
|
|
|
lock (m_mapBlockRequestEvent)
|
|
{
|
|
if (m_mapBlockRequests.ContainsKey(avatar.UUID))
|
|
m_mapBlockRequests.Remove(avatar.UUID);
|
|
}
|
|
}
|
|
|
|
public void OnRegionUp(GridRegion otherRegion)
|
|
{
|
|
ulong regionhandle = otherRegion.RegionHandle;
|
|
string httpserver = otherRegion.ServerURI + "MAP/MapItems/" + regionhandle.ToString();
|
|
|
|
lock (m_blacklistedregions)
|
|
{
|
|
if (m_blacklistedregions.Contains(regionhandle))
|
|
m_blacklistedregions.Remove(regionhandle);
|
|
}
|
|
|
|
lock (m_blacklistedurls)
|
|
{
|
|
if (m_blacklistedurls.Contains(httpserver))
|
|
m_blacklistedurls.Remove(httpserver);
|
|
}
|
|
|
|
lock (m_cachedRegionMapItemsAddress)
|
|
{
|
|
m_cachedRegionMapItemsAddress.AddOrUpdate(regionhandle,
|
|
httpserver, 5.0 * expireBlackListTime);
|
|
}
|
|
}
|
|
|
|
private Byte[] GenerateOverlay()
|
|
{
|
|
int landTileSize = LandManagementModule.LandUnit;
|
|
|
|
// These need to be ints for bitmap generation
|
|
int regionSizeX = (int)m_scene.RegionInfo.RegionSizeX;
|
|
int regionLandTilesX = regionSizeX / landTileSize;
|
|
|
|
int regionSizeY = (int)m_scene.RegionInfo.RegionSizeY;
|
|
int regionLandTilesY = regionSizeY / landTileSize;
|
|
|
|
bool landForSale = false;
|
|
ILandObject land;
|
|
|
|
// scan terrain avoiding potencial merges of large bitmaps
|
|
//TODO create the sell bitmap at landchannel / landmaster ?
|
|
// and auction also, still not suported
|
|
|
|
bool[,] saleBitmap = new bool[regionLandTilesX, regionLandTilesY];
|
|
for (int x = 0, xx = 0; x < regionLandTilesX; x++ ,xx += landTileSize)
|
|
{
|
|
for (int y = 0, yy = 0; y < regionLandTilesY; y++, yy += landTileSize)
|
|
{
|
|
land = m_scene.LandChannel.GetLandObject(xx, yy);
|
|
if (land != null && (land.LandData.Flags & (uint)ParcelFlags.ForSale) != 0)
|
|
{
|
|
saleBitmap[x, y] = true;
|
|
landForSale = true;
|
|
}
|
|
else
|
|
saleBitmap[x, y] = false;
|
|
}
|
|
}
|
|
|
|
if (!landForSale)
|
|
{
|
|
m_log.DebugFormat("[WORLD MAP]: Region {0} has no parcels for sale, not generating overlay", m_scene.RegionInfo.RegionName);
|
|
return null;
|
|
}
|
|
|
|
m_log.DebugFormat("[WORLD MAP]: Region {0} has parcels for sale, generating overlay", m_scene.RegionInfo.RegionName);
|
|
|
|
using (Bitmap overlay = new Bitmap(regionSizeX, regionSizeY))
|
|
{
|
|
Color background = Color.FromArgb(0, 0, 0, 0);
|
|
|
|
using (Graphics g = Graphics.FromImage(overlay))
|
|
{
|
|
using (SolidBrush transparent = new SolidBrush(background))
|
|
g.FillRectangle(transparent, 0, 0, regionSizeX, regionSizeY);
|
|
|
|
// make it a bit transparent
|
|
using (SolidBrush yellow = new SolidBrush(Color.FromArgb(192, 249, 223, 9)))
|
|
{
|
|
for (int x = 0; x < regionLandTilesX; x++)
|
|
{
|
|
for (int y = 0; y < regionLandTilesY; y++)
|
|
{
|
|
if (saleBitmap[x, y])
|
|
g.FillRectangle(
|
|
yellow,
|
|
x * landTileSize,
|
|
regionSizeX - landTileSize - (y * landTileSize),
|
|
landTileSize,
|
|
landTileSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
return OpenJPEG.EncodeFromImage(overlay, false);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.DebugFormat("[WORLD MAP]: Error creating parcel overlay: " + e.ToString());
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public class MapRequestState
|
|
{
|
|
public UUID agentID;
|
|
public uint flags;
|
|
public uint EstateID;
|
|
public bool godlike;
|
|
public uint itemtype;
|
|
public ulong regionhandle;
|
|
}
|
|
|
|
public struct MapBlockRequestData
|
|
{
|
|
public IClientAPI client;
|
|
public int minX;
|
|
public int minY;
|
|
public int maxX;
|
|
public int maxY;
|
|
public uint flags;
|
|
}
|
|
}
|