opensim-development – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
4 | * |
||
5 | * Redistribution and use in source and binary forms, with or without |
||
6 | * modification, are permitted provided that the following conditions are met: |
||
7 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the OpenSimulator Project nor the |
||
13 | * names of its contributors may be used to endorse or promote products |
||
14 | * derived from this software without specific prior written permission. |
||
15 | * |
||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
26 | */ |
||
27 | |||
28 | using System; |
||
29 | using System.Collections; |
||
30 | using System.Collections.Generic; |
||
31 | using System.Drawing; |
||
32 | using System.Drawing.Imaging; |
||
33 | using System.IO; |
||
34 | using System.Net; |
||
35 | using System.Reflection; |
||
36 | using System.Runtime.Remoting.Messaging; |
||
37 | using System.Threading; |
||
38 | using log4net; |
||
39 | using Nini.Config; |
||
40 | using OpenMetaverse; |
||
41 | using OpenMetaverse.Imaging; |
||
42 | using OpenMetaverse.StructuredData; |
||
43 | using Mono.Addins; |
||
44 | using OpenSim.Framework; |
||
45 | using OpenSim.Framework.Capabilities; |
||
46 | using OpenSim.Framework.Monitoring; |
||
47 | using OpenSim.Framework.Servers; |
||
48 | using OpenSim.Framework.Servers.HttpServer; |
||
49 | using OpenSim.Region.Framework.Interfaces; |
||
50 | using OpenSim.Region.Framework.Scenes; |
||
51 | using OpenSim.Region.CoreModules.World.Land; |
||
52 | using Caps=OpenSim.Framework.Capabilities.Caps; |
||
53 | using OSDArray=OpenMetaverse.StructuredData.OSDArray; |
||
54 | using OSDMap=OpenMetaverse.StructuredData.OSDMap; |
||
55 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; |
||
56 | |||
57 | namespace OpenSim.Region.CoreModules.World.WorldMap |
||
58 | { |
||
59 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WorldMapModule")] |
||
60 | public class WorldMapModule : INonSharedRegionModule, IWorldMapModule |
||
61 | { |
||
62 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
63 | private static string LogHeader = "[WORLD MAP]"; |
||
64 | |||
65 | private static readonly string DEFAULT_WORLD_MAP_EXPORT_PATH = "exportmap.jpg"; |
||
66 | private static readonly UUID STOP_UUID = UUID.Random(); |
||
67 | private static readonly string m_mapLayerPath = "0001/"; |
||
68 | |||
69 | private OpenSim.Framework.BlockingQueue<MapRequestState> requests = new OpenSim.Framework.BlockingQueue<MapRequestState>(); |
||
70 | |||
71 | protected Scene m_scene; |
||
72 | private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>(); |
||
73 | private int cachedTime = 0; |
||
74 | private int blacklistTimeout = 10*60*1000; // 10 minutes |
||
75 | private byte[] myMapImageJPEG; |
||
76 | protected volatile bool m_Enabled = false; |
||
77 | private Dictionary<UUID, MapRequestState> m_openRequests = new Dictionary<UUID, MapRequestState>(); |
||
78 | private Dictionary<string, int> m_blacklistedurls = new Dictionary<string, int>(); |
||
79 | private Dictionary<ulong, int> m_blacklistedregions = new Dictionary<ulong, int>(); |
||
80 | private Dictionary<ulong, string> m_cachedRegionMapItemsAddress = new Dictionary<ulong, string>(); |
||
81 | private List<UUID> m_rootAgents = new List<UUID>(); |
||
82 | private volatile bool threadrunning = false; |
||
83 | |||
84 | private IServiceThrottleModule m_ServiceThrottle; |
||
85 | |||
86 | //private int CacheRegionsDistance = 256; |
||
87 | |||
88 | #region INonSharedRegionModule Members |
||
89 | public virtual void Initialise (IConfigSource config) |
||
90 | { |
||
91 | string[] configSections = new string[] { "Map", "Startup" }; |
||
92 | |||
93 | if (Util.GetConfigVarFromSections<string>( |
||
94 | config, "WorldMapModule", configSections, "WorldMap") == "WorldMap") |
||
95 | m_Enabled = true; |
||
96 | |||
97 | blacklistTimeout |
||
98 | = Util.GetConfigVarFromSections<int>(config, "BlacklistTimeout", configSections, 10 * 60) * 1000; |
||
99 | } |
||
100 | |||
101 | public virtual void AddRegion (Scene scene) |
||
102 | { |
||
103 | if (!m_Enabled) |
||
104 | return; |
||
105 | |||
106 | lock (scene) |
||
107 | { |
||
108 | m_scene = scene; |
||
109 | |||
110 | m_scene.RegisterModuleInterface<IWorldMapModule>(this); |
||
111 | |||
112 | m_scene.AddCommand( |
||
113 | "Regions", this, "export-map", |
||
114 | "export-map [<path>]", |
||
115 | "Save an image of the world map", HandleExportWorldMapConsoleCommand); |
||
116 | |||
117 | m_scene.AddCommand( |
||
118 | "Regions", this, "generate map", |
||
119 | "generate map", |
||
120 | "Generates and stores a new maptile.", HandleGenerateMapConsoleCommand); |
||
121 | |||
122 | AddHandlers(); |
||
123 | } |
||
124 | } |
||
125 | |||
126 | public virtual void RemoveRegion (Scene scene) |
||
127 | { |
||
128 | if (!m_Enabled) |
||
129 | return; |
||
130 | |||
131 | lock (m_scene) |
||
132 | { |
||
133 | m_Enabled = false; |
||
134 | RemoveHandlers(); |
||
135 | m_scene = null; |
||
136 | } |
||
137 | } |
||
138 | |||
139 | public virtual void RegionLoaded (Scene scene) |
||
140 | { |
||
141 | if (!m_Enabled) |
||
142 | return; |
||
143 | |||
144 | m_ServiceThrottle = scene.RequestModuleInterface<IServiceThrottleModule>(); |
||
145 | } |
||
146 | |||
147 | |||
148 | public virtual void Close() |
||
149 | { |
||
150 | } |
||
151 | |||
152 | public Type ReplaceableInterface |
||
153 | { |
||
154 | get { return null; } |
||
155 | } |
||
156 | |||
157 | public virtual string Name |
||
158 | { |
||
159 | get { return "WorldMapModule"; } |
||
160 | } |
||
161 | |||
162 | #endregion |
||
163 | |||
164 | // this has to be called with a lock on m_scene |
||
165 | protected virtual void AddHandlers() |
||
166 | { |
||
167 | myMapImageJPEG = new byte[0]; |
||
168 | |||
169 | string regionimage = "regionImage" + m_scene.RegionInfo.RegionID.ToString(); |
||
170 | regionimage = regionimage.Replace("-", ""); |
||
171 | m_log.Info("[WORLD MAP]: JPEG Map location: " + m_scene.RegionInfo.ServerURI + "index.php?method=" + regionimage); |
||
172 | |||
173 | MainServer.Instance.AddHTTPHandler(regionimage, |
||
174 | new GenericHTTPDOSProtector(OnHTTPGetMapImage, OnHTTPThrottled, new BasicDosProtectorOptions() |
||
175 | { |
||
176 | AllowXForwardedFor = false, |
||
177 | ForgetTimeSpan = TimeSpan.FromMinutes(2), |
||
178 | MaxRequestsInTimeframe = 4, |
||
179 | ReportingName = "MAPDOSPROTECTOR", |
||
180 | RequestTimeSpan = TimeSpan.FromSeconds(10), |
||
181 | ThrottledAction = BasicDOSProtector.ThrottleAction.DoThrottledMethod |
||
182 | }).Process); |
||
183 | MainServer.Instance.AddLLSDHandler( |
||
184 | "/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString(), HandleRemoteMapItemRequest); |
||
185 | |||
186 | m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; |
||
187 | m_scene.EventManager.OnNewClient += OnNewClient; |
||
188 | m_scene.EventManager.OnClientClosed += ClientLoggedOut; |
||
189 | m_scene.EventManager.OnMakeChildAgent += MakeChildAgent; |
||
190 | m_scene.EventManager.OnMakeRootAgent += MakeRootAgent; |
||
191 | m_scene.EventManager.OnRegionUp += OnRegionUp; |
||
192 | |||
193 | // StartThread(new object()); |
||
194 | } |
||
195 | |||
196 | // this has to be called with a lock on m_scene |
||
197 | protected virtual void RemoveHandlers() |
||
198 | { |
||
199 | // StopThread(); |
||
200 | |||
201 | m_scene.EventManager.OnRegionUp -= OnRegionUp; |
||
202 | m_scene.EventManager.OnMakeRootAgent -= MakeRootAgent; |
||
203 | m_scene.EventManager.OnMakeChildAgent -= MakeChildAgent; |
||
204 | m_scene.EventManager.OnClientClosed -= ClientLoggedOut; |
||
205 | m_scene.EventManager.OnNewClient -= OnNewClient; |
||
206 | m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; |
||
207 | |||
208 | string regionimage = "regionImage" + m_scene.RegionInfo.RegionID.ToString(); |
||
209 | regionimage = regionimage.Replace("-", ""); |
||
210 | MainServer.Instance.RemoveLLSDHandler("/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString(), |
||
211 | HandleRemoteMapItemRequest); |
||
212 | MainServer.Instance.RemoveHTTPHandler("", regionimage); |
||
213 | } |
||
214 | |||
215 | public void OnRegisterCaps(UUID agentID, Caps caps) |
||
216 | { |
||
217 | //m_log.DebugFormat("[WORLD MAP]: OnRegisterCaps: agentID {0} caps {1}", agentID, caps); |
||
218 | string capsBase = "/CAPS/" + caps.CapsObjectPath; |
||
219 | caps.RegisterHandler( |
||
220 | "MapLayer", |
||
221 | new RestStreamHandler( |
||
222 | "POST", |
||
223 | capsBase + m_mapLayerPath, |
||
224 | (request, path, param, httpRequest, httpResponse) |
||
225 | => MapLayerRequest(request, path, param, agentID, caps), |
||
226 | "MapLayer", |
||
227 | agentID.ToString())); |
||
228 | } |
||
229 | |||
230 | /// <summary> |
||
231 | /// Callback for a map layer request |
||
232 | /// </summary> |
||
233 | /// <param name="request"></param> |
||
234 | /// <param name="path"></param> |
||
235 | /// <param name="param"></param> |
||
236 | /// <param name="agentID"></param> |
||
237 | /// <param name="caps"></param> |
||
238 | /// <returns></returns> |
||
239 | public string MapLayerRequest(string request, string path, string param, |
||
240 | UUID agentID, Caps caps) |
||
241 | { |
||
242 | //try |
||
243 | // |
||
244 | //m_log.DebugFormat("[MAPLAYER]: path: {0}, param: {1}, agent:{2}", |
||
245 | // path, param, agentID.ToString()); |
||
246 | |||
247 | // There is a major hack going on in this method. The viewer doesn't request |
||
248 | // map blocks (RequestMapBlocks) above 2048. That means that if we don't hack, |
||
249 | // grids above that cell don't have a map at all. So, here's the hack: we wait |
||
250 | // for this CAP request to come, and we inject the map blocks at this point. |
||
251 | // In a normal scenario, this request simply sends back the MapLayer (the blue color). |
||
252 | // In the hacked scenario, it also sends the map blocks via UDP. |
||
253 | // |
||
254 | // 6/8/2011 -- I'm adding an explicit 2048 check, so that we never forget that there is |
||
255 | // a hack here, and so that regions below 4096 don't get spammed with unnecessary map blocks. |
||
256 | |||
257 | if (m_scene.RegionInfo.RegionLocX >= 2048 || m_scene.RegionInfo.RegionLocY >= 2048) |
||
258 | { |
||
259 | ScenePresence avatarPresence = null; |
||
260 | |||
261 | m_scene.TryGetScenePresence(agentID, out avatarPresence); |
||
262 | |||
263 | if (avatarPresence != null) |
||
264 | { |
||
265 | bool lookup = false; |
||
266 | |||
267 | lock (cachedMapBlocks) |
||
268 | { |
||
269 | if (cachedMapBlocks.Count > 0 && ((cachedTime + 1800) > Util.UnixTimeSinceEpoch())) |
||
270 | { |
||
271 | List<MapBlockData> mapBlocks; |
||
272 | |||
273 | mapBlocks = cachedMapBlocks; |
||
274 | avatarPresence.ControllingClient.SendMapBlock(mapBlocks, 0); |
||
275 | } |
||
276 | else |
||
277 | { |
||
278 | lookup = true; |
||
279 | } |
||
280 | } |
||
281 | if (lookup) |
||
282 | { |
||
283 | List<MapBlockData> mapBlocks = new List<MapBlockData>(); ; |
||
284 | |||
285 | // Get regions that are within 8 regions of here |
||
286 | List<GridRegion> regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, |
||
287 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX - 8), |
||
288 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX + 8), |
||
289 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY - 8), |
||
290 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY + 8) ); |
||
291 | foreach (GridRegion r in regions) |
||
292 | { |
||
293 | MapBlockData block = MapBlockFromGridRegion(r, 0); |
||
294 | mapBlocks.Add(block); |
||
295 | } |
||
296 | avatarPresence.ControllingClient.SendMapBlock(mapBlocks, 0); |
||
297 | |||
298 | lock (cachedMapBlocks) |
||
299 | cachedMapBlocks = mapBlocks; |
||
300 | |||
301 | cachedTime = Util.UnixTimeSinceEpoch(); |
||
302 | } |
||
303 | } |
||
304 | } |
||
305 | |||
306 | LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse(); |
||
307 | mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse()); |
||
308 | return mapResponse.ToString(); |
||
309 | } |
||
310 | |||
311 | /// <summary> |
||
312 | /// |
||
313 | /// </summary> |
||
314 | /// <param name="mapReq"></param> |
||
315 | /// <returns></returns> |
||
316 | public LLSDMapLayerResponse GetMapLayer(LLSDMapRequest mapReq) |
||
317 | { |
||
318 | // m_log.DebugFormat("[WORLD MAP]: MapLayer Request in region: {0}", m_scene.RegionInfo.RegionName); |
||
319 | LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse(); |
||
320 | mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse()); |
||
321 | return mapResponse; |
||
322 | } |
||
323 | |||
324 | /// <summary> |
||
325 | /// |
||
326 | /// </summary> |
||
327 | /// <returns></returns> |
||
328 | protected static OSDMapLayer GetOSDMapLayerResponse() |
||
329 | { |
||
330 | OSDMapLayer mapLayer = new OSDMapLayer(); |
||
331 | mapLayer.Right = 5000; |
||
332 | mapLayer.Top = 5000; |
||
333 | mapLayer.ImageID = new UUID("00000000-0000-1111-9999-000000000006"); |
||
334 | |||
335 | return mapLayer; |
||
336 | } |
||
337 | #region EventHandlers |
||
338 | |||
339 | /// <summary> |
||
340 | /// Registered for event |
||
341 | /// </summary> |
||
342 | /// <param name="client"></param> |
||
343 | private void OnNewClient(IClientAPI client) |
||
344 | { |
||
345 | client.OnRequestMapBlocks += RequestMapBlocks; |
||
346 | client.OnMapItemRequest += HandleMapItemRequest; |
||
347 | } |
||
348 | |||
349 | /// <summary> |
||
350 | /// Client logged out, check to see if there are any more root agents in the simulator |
||
351 | /// If not, stop the mapItemRequest Thread |
||
352 | /// Event handler |
||
353 | /// </summary> |
||
354 | /// <param name="AgentId">AgentID that logged out</param> |
||
355 | private void ClientLoggedOut(UUID AgentId, Scene scene) |
||
356 | { |
||
357 | lock (m_rootAgents) |
||
358 | { |
||
359 | m_rootAgents.Remove(AgentId); |
||
360 | } |
||
361 | } |
||
362 | #endregion |
||
363 | |||
364 | /// <summary> |
||
365 | /// Starts the MapItemRequest Thread |
||
366 | /// Note that this only gets started when there are actually agents in the region |
||
367 | /// Additionally, it gets stopped when there are none. |
||
368 | /// </summary> |
||
369 | /// <param name="o"></param> |
||
370 | private void StartThread(object o) |
||
371 | { |
||
372 | if (threadrunning) return; |
||
373 | threadrunning = true; |
||
374 | |||
375 | // m_log.Debug("[WORLD MAP]: Starting remote MapItem request thread"); |
||
376 | |||
377 | Watchdog.StartThread( |
||
378 | process, |
||
379 | string.Format("MapItemRequestThread ({0})", m_scene.RegionInfo.RegionName), |
||
380 | ThreadPriority.BelowNormal, |
||
381 | true, |
||
382 | true); |
||
383 | } |
||
384 | |||
385 | /// <summary> |
||
386 | /// Enqueues a 'stop thread' MapRequestState. Causes the MapItemRequest thread to end |
||
387 | /// </summary> |
||
388 | private void StopThread() |
||
389 | { |
||
390 | MapRequestState st = new MapRequestState(); |
||
391 | st.agentID = STOP_UUID; |
||
392 | st.EstateID=0; |
||
393 | st.flags=0; |
||
394 | st.godlike=false; |
||
395 | st.itemtype=0; |
||
396 | st.regionhandle=0; |
||
397 | |||
398 | requests.Enqueue(st); |
||
399 | } |
||
400 | |||
401 | public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags, |
||
402 | uint EstateID, bool godlike, uint itemtype, ulong regionhandle) |
||
403 | { |
||
404 | // m_log.DebugFormat("[WORLD MAP]: Handle MapItem request {0} {1}", regionhandle, itemtype); |
||
405 | |||
406 | lock (m_rootAgents) |
||
407 | { |
||
408 | if (!m_rootAgents.Contains(remoteClient.AgentId)) |
||
409 | return; |
||
410 | } |
||
411 | uint xstart = 0; |
||
412 | uint ystart = 0; |
||
413 | Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle, out xstart, out ystart); |
||
414 | if (itemtype == (int)GridItemType.AgentLocations) |
||
415 | { |
||
416 | if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) |
||
417 | { |
||
418 | // Just requesting map info about the current, local region |
||
419 | int tc = Environment.TickCount; |
||
420 | List<mapItemReply> mapitems = new List<mapItemReply>(); |
||
421 | mapItemReply mapitem = new mapItemReply(); |
||
422 | if (m_scene.GetRootAgentCount() <= 1) |
||
423 | { |
||
424 | mapitem = new mapItemReply( |
||
425 | xstart + 1, |
||
426 | ystart + 1, |
||
427 | UUID.Zero, |
||
428 | Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()), |
||
429 | 0, 0); |
||
430 | mapitems.Add(mapitem); |
||
431 | } |
||
432 | else |
||
433 | { |
||
434 | m_scene.ForEachRootScenePresence(delegate(ScenePresence sp) |
||
435 | { |
||
436 | // Don't send a green dot for yourself |
||
437 | if (sp.UUID != remoteClient.AgentId) |
||
438 | { |
||
439 | mapitem = new mapItemReply( |
||
440 | xstart + (uint)sp.AbsolutePosition.X, |
||
441 | ystart + (uint)sp.AbsolutePosition.Y, |
||
442 | UUID.Zero, |
||
443 | Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()), |
||
444 | 1, 0); |
||
445 | mapitems.Add(mapitem); |
||
446 | } |
||
447 | }); |
||
448 | } |
||
449 | remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); |
||
450 | } |
||
451 | else |
||
452 | { |
||
453 | // Remote Map Item Request |
||
454 | |||
455 | // ensures that the blockingqueue doesn't get borked if the GetAgents() timing changes. |
||
456 | RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); |
||
457 | } |
||
458 | } |
||
459 | else if (itemtype == (int)GridItemType.LandForSale) // Service 7 (MAP_ITEM_LAND_FOR_SALE) |
||
460 | { |
||
461 | if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) |
||
462 | { |
||
463 | // Parcels |
||
464 | ILandChannel landChannel = m_scene.LandChannel; |
||
465 | List<ILandObject> parcels = landChannel.AllParcels(); |
||
466 | |||
467 | // Local Map Item Request |
||
468 | List<mapItemReply> mapitems = new List<mapItemReply>(); |
||
469 | mapItemReply mapitem = new mapItemReply(); |
||
470 | if ((parcels != null) && (parcels.Count >= 1)) |
||
471 | { |
||
472 | foreach (ILandObject parcel_interface in parcels) |
||
473 | { |
||
474 | // Play it safe |
||
475 | if (!(parcel_interface is LandObject)) |
||
476 | continue; |
||
477 | |||
478 | LandObject land = (LandObject)parcel_interface; |
||
479 | LandData parcel = land.LandData; |
||
480 | |||
481 | // Show land for sale |
||
482 | if ((parcel.Flags & (uint)ParcelFlags.ForSale) == (uint)ParcelFlags.ForSale) |
||
483 | { |
||
484 | Vector3 min = parcel.AABBMin; |
||
485 | Vector3 max = parcel.AABBMax; |
||
486 | float x = (min.X+max.X)/2; |
||
487 | float y = (min.Y+max.Y)/2; |
||
488 | |||
489 | mapitem = new mapItemReply( |
||
490 | xstart + (uint)x, |
||
491 | ystart + (uint)y, |
||
492 | parcel.GlobalID, |
||
493 | parcel.Name, |
||
494 | parcel.Area, |
||
495 | parcel.SalePrice |
||
496 | ); |
||
497 | mapitems.Add(mapitem); |
||
498 | } |
||
499 | } |
||
500 | } |
||
501 | remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); |
||
502 | } |
||
503 | else |
||
504 | { |
||
505 | // Remote Map Item Request |
||
506 | |||
507 | // ensures that the blockingqueue doesn't get borked if the GetAgents() timing changes. |
||
508 | RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); |
||
509 | } |
||
510 | } |
||
511 | else if (itemtype == (int)GridItemType.Telehub) // Service 1 (MAP_ITEM_TELEHUB) |
||
512 | { |
||
513 | if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) |
||
514 | { |
||
515 | List<mapItemReply> mapitems = new List<mapItemReply>(); |
||
516 | mapItemReply mapitem = new mapItemReply(); |
||
517 | |||
518 | SceneObjectGroup sog = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject); |
||
519 | if (sog != null) |
||
520 | { |
||
521 | mapitem = new mapItemReply( |
||
522 | xstart + (uint)sog.AbsolutePosition.X, |
||
523 | ystart + (uint)sog.AbsolutePosition.Y, |
||
524 | UUID.Zero, |
||
525 | sog.Name, |
||
526 | 0, // color (not used) |
||
527 | |||
528 | ); |
||
529 | mapitems.Add(mapitem); |
||
530 | |||
531 | remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); |
||
532 | } |
||
533 | } |
||
534 | else |
||
535 | { |
||
536 | // Remote Map Item Request |
||
537 | RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); |
||
538 | } |
||
539 | } |
||
540 | } |
||
541 | |||
542 | private int nAsyncRequests = 0; |
||
543 | /// <summary> |
||
544 | /// Processing thread main() loop for doing remote mapitem requests |
||
545 | /// </summary> |
||
546 | public void process() |
||
547 | { |
||
548 | //const int MAX_ASYNC_REQUESTS = 20; |
||
549 | try |
||
550 | { |
||
551 | while (true) |
||
552 | { |
||
553 | MapRequestState st = requests.Dequeue(1000); |
||
554 | |||
555 | // end gracefully |
||
556 | if (st.agentID == STOP_UUID) |
||
557 | break; |
||
558 | |||
559 | if (st.agentID != UUID.Zero) |
||
560 | { |
||
561 | bool dorequest = true; |
||
562 | lock (m_rootAgents) |
||
563 | { |
||
564 | if (!m_rootAgents.Contains(st.agentID)) |
||
565 | dorequest = false; |
||
566 | } |
||
567 | |||
568 | if (dorequest && !m_blacklistedregions.ContainsKey(st.regionhandle)) |
||
569 | { |
||
570 | while (nAsyncRequests >= MAX_ASYNC_REQUESTS) // hit the break |
||
571 | Thread.Sleep(80); |
||
572 | |||
573 | RequestMapItemsDelegate d = RequestMapItemsAsync; |
||
574 | d.BeginInvoke(st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle, RequestMapItemsCompleted, null); |
||
575 | //OSDMap response = RequestMapItemsAsync(st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle); |
||
576 | //RequestMapItemsCompleted(response); |
||
577 | Interlocked.Increment(ref nAsyncRequests); |
||
578 | } |
||
579 | } |
||
580 | |||
581 | Watchdog.UpdateThread(); |
||
582 | } |
||
583 | } |
||
584 | catch (Exception e) |
||
585 | { |
||
586 | m_log.ErrorFormat("[WORLD MAP]: Map item request thread terminated abnormally with exception {0}", e); |
||
587 | } |
||
588 | |||
589 | threadrunning = false; |
||
590 | Watchdog.RemoveThread(); |
||
591 | } |
||
592 | |||
593 | const int MAX_ASYNC_REQUESTS = 20; |
||
594 | |||
595 | /// <summary> |
||
596 | /// Enqueues the map item request into the services throttle processing thread |
||
597 | /// </summary> |
||
598 | /// <param name="state"></param> |
||
599 | public void EnqueueMapItemRequest(MapRequestState st) |
||
600 | { |
||
601 | |||
602 | m_ServiceThrottle.Enqueue("map-item", st.regionhandle.ToString() + st.agentID.ToString(), delegate |
||
603 | { |
||
604 | if (st.agentID != UUID.Zero) |
||
605 | { |
||
606 | bool dorequest = true; |
||
607 | lock (m_rootAgents) |
||
608 | { |
||
609 | if (!m_rootAgents.Contains(st.agentID)) |
||
610 | dorequest = false; |
||
611 | } |
||
612 | |||
613 | if (dorequest && !m_blacklistedregions.ContainsKey(st.regionhandle)) |
||
614 | { |
||
615 | if (nAsyncRequests >= MAX_ASYNC_REQUESTS) // hit the break |
||
616 | { |
||
617 | // AH!!! Recursive ! |
||
618 | // Put this request back in the queue and return |
||
619 | EnqueueMapItemRequest(st); |
||
620 | return; |
||
621 | } |
||
622 | |||
623 | RequestMapItemsDelegate d = RequestMapItemsAsync; |
||
624 | d.BeginInvoke(st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle, RequestMapItemsCompleted, null); |
||
625 | //OSDMap response = RequestMapItemsAsync(st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle); |
||
626 | //RequestMapItemsCompleted(response); |
||
627 | Interlocked.Increment(ref nAsyncRequests); |
||
628 | } |
||
629 | } |
||
630 | }); |
||
631 | } |
||
632 | |||
633 | /// <summary> |
||
634 | /// Sends the mapitem response to the IClientAPI |
||
635 | /// </summary> |
||
636 | /// <param name="response">The OSDMap Response for the mapitem</param> |
||
637 | private void RequestMapItemsCompleted(IAsyncResult iar) |
||
638 | { |
||
639 | AsyncResult result = (AsyncResult)iar; |
||
640 | RequestMapItemsDelegate icon = (RequestMapItemsDelegate)result.AsyncDelegate; |
||
641 | |||
642 | OSDMap response = (OSDMap)icon.EndInvoke(iar); |
||
643 | |||
644 | Interlocked.Decrement(ref nAsyncRequests); |
||
645 | |||
646 | if (!response.ContainsKey("requestID")) |
||
647 | return; |
||
648 | |||
649 | UUID requestID = response["requestID"].AsUUID(); |
||
650 | |||
651 | if (requestID != UUID.Zero) |
||
652 | { |
||
653 | MapRequestState mrs = new MapRequestState(); |
||
654 | mrs.agentID = UUID.Zero; |
||
655 | lock (m_openRequests) |
||
656 | { |
||
657 | if (m_openRequests.ContainsKey(requestID)) |
||
658 | { |
||
659 | mrs = m_openRequests[requestID]; |
||
660 | m_openRequests.Remove(requestID); |
||
661 | } |
||
662 | } |
||
663 | |||
664 | if (mrs.agentID != UUID.Zero) |
||
665 | { |
||
666 | ScenePresence av = null; |
||
667 | m_scene.TryGetScenePresence(mrs.agentID, out av); |
||
668 | if (av != null) |
||
669 | { |
||
670 | if (response.ContainsKey(mrs.itemtype.ToString())) |
||
671 | { |
||
672 | List<mapItemReply> returnitems = new List<mapItemReply>(); |
||
673 | OSDArray itemarray = (OSDArray)response[mrs.itemtype.ToString()]; |
||
674 | for (int i = 0; i < itemarray.Count; i++) |
||
675 | { |
||
676 | OSDMap mapitem = (OSDMap)itemarray[i]; |
||
677 | mapItemReply mi = new mapItemReply(); |
||
678 | mi.FromOSD(mapitem); |
||
679 | returnitems.Add(mi); |
||
680 | } |
||
681 | av.ControllingClient.SendMapItemReply(returnitems.ToArray(), mrs.itemtype, mrs.flags); |
||
682 | } |
||
683 | |||
684 | // Service 7 (MAP_ITEM_LAND_FOR_SALE) |
||
685 | uint itemtype = (uint)GridItemType.LandForSale; |
||
686 | |||
687 | if (response.ContainsKey(itemtype.ToString())) |
||
688 | { |
||
689 | List<mapItemReply> returnitems = new List<mapItemReply>(); |
||
690 | OSDArray itemarray = (OSDArray)response[itemtype.ToString()]; |
||
691 | for (int i = 0; i < itemarray.Count; i++) |
||
692 | { |
||
693 | OSDMap mapitem = (OSDMap)itemarray[i]; |
||
694 | mapItemReply mi = new mapItemReply(); |
||
695 | mi.FromOSD(mapitem); |
||
696 | returnitems.Add(mi); |
||
697 | } |
||
698 | av.ControllingClient.SendMapItemReply(returnitems.ToArray(), itemtype, mrs.flags); |
||
699 | } |
||
700 | |||
701 | // Service 1 (MAP_ITEM_TELEHUB) |
||
702 | itemtype = (uint)GridItemType.Telehub; |
||
703 | |||
704 | if (response.ContainsKey(itemtype.ToString())) |
||
705 | { |
||
706 | List<mapItemReply> returnitems = new List<mapItemReply>(); |
||
707 | OSDArray itemarray = (OSDArray)response[itemtype.ToString()]; |
||
708 | for (int i = 0; i < itemarray.Count; i++) |
||
709 | { |
||
710 | OSDMap mapitem = (OSDMap)itemarray[i]; |
||
711 | mapItemReply mi = new mapItemReply(); |
||
712 | mi.FromOSD(mapitem); |
||
713 | returnitems.Add(mi); |
||
714 | } |
||
715 | av.ControllingClient.SendMapItemReply(returnitems.ToArray(), itemtype, mrs.flags); |
||
716 | } |
||
717 | } |
||
718 | } |
||
719 | } |
||
720 | } |
||
721 | |||
722 | /// <summary> |
||
723 | /// Enqueue the MapItem request for remote processing |
||
724 | /// </summary> |
||
725 | /// <param name="httpserver">blank string, we discover this in the process</param> |
||
726 | /// <param name="id">Agent ID that we are making this request on behalf</param> |
||
727 | /// <param name="flags">passed in from packet</param> |
||
728 | /// <param name="EstateID">passed in from packet</param> |
||
729 | /// <param name="godlike">passed in from packet</param> |
||
730 | /// <param name="itemtype">passed in from packet</param> |
||
731 | /// <param name="regionhandle">Region we're looking up</param> |
||
732 | public void RequestMapItems(string httpserver, UUID id, uint flags, |
||
733 | uint EstateID, bool godlike, uint itemtype, ulong regionhandle) |
||
734 | { |
||
735 | MapRequestState st = new MapRequestState(); |
||
736 | st.agentID = id; |
||
737 | st.flags = flags; |
||
738 | st.EstateID = EstateID; |
||
739 | st.godlike = godlike; |
||
740 | st.itemtype = itemtype; |
||
741 | st.regionhandle = regionhandle; |
||
742 | EnqueueMapItemRequest(st); |
||
743 | } |
||
744 | |||
745 | private delegate OSDMap RequestMapItemsDelegate(UUID id, uint flags, |
||
746 | uint EstateID, bool godlike, uint itemtype, ulong regionhandle); |
||
747 | /// <summary> |
||
748 | /// Does the actual remote mapitem request |
||
749 | /// This should be called from an asynchronous thread |
||
750 | /// Request failures get blacklisted until region restart so we don't |
||
751 | /// continue to spend resources trying to contact regions that are down. |
||
752 | /// </summary> |
||
753 | /// <param name="httpserver">blank string, we discover this in the process</param> |
||
754 | /// <param name="id">Agent ID that we are making this request on behalf</param> |
||
755 | /// <param name="flags">passed in from packet</param> |
||
756 | /// <param name="EstateID">passed in from packet</param> |
||
757 | /// <param name="godlike">passed in from packet</param> |
||
758 | /// <param name="itemtype">passed in from packet</param> |
||
759 | /// <param name="regionhandle">Region we're looking up</param> |
||
760 | /// <returns></returns> |
||
761 | private OSDMap RequestMapItemsAsync(UUID id, uint flags, |
||
762 | uint EstateID, bool godlike, uint itemtype, ulong regionhandle) |
||
763 | { |
||
764 | // m_log.DebugFormat("[WORLDMAP]: RequestMapItemsAsync; region handle: {0} {1}", regionhandle, itemtype); |
||
765 | |||
766 | string httpserver = ""; |
||
767 | bool blacklisted = false; |
||
768 | lock (m_blacklistedregions) |
||
769 | { |
||
770 | if (m_blacklistedregions.ContainsKey(regionhandle)) |
||
771 | { |
||
772 | if (Environment.TickCount > (m_blacklistedregions[regionhandle] + blacklistTimeout)) |
||
773 | { |
||
774 | m_log.DebugFormat("[WORLD MAP]: Unblock blacklisted region {0}", regionhandle); |
||
775 | |||
776 | m_blacklistedregions.Remove(regionhandle); |
||
777 | } |
||
778 | else |
||
779 | blacklisted = true; |
||
780 | } |
||
781 | } |
||
782 | |||
783 | if (blacklisted) |
||
784 | return new OSDMap(); |
||
785 | |||
786 | UUID requestID = UUID.Random(); |
||
787 | lock (m_cachedRegionMapItemsAddress) |
||
788 | { |
||
789 | if (m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) |
||
790 | httpserver = m_cachedRegionMapItemsAddress[regionhandle]; |
||
791 | } |
||
792 | if (httpserver.Length == 0) |
||
793 | { |
||
794 | uint x = 0, y = 0; |
||
795 | Util.RegionHandleToWorldLoc(regionhandle, out x, out y); |
||
796 | GridRegion mreg = m_scene.GridService.GetRegionByPosition(m_scene.RegionInfo.ScopeID, (int)x, (int)y); |
||
797 | |||
798 | if (mreg != null) |
||
799 | { |
||
800 | httpserver = mreg.ServerURI + "MAP/MapItems/" + regionhandle.ToString(); |
||
801 | lock (m_cachedRegionMapItemsAddress) |
||
802 | { |
||
803 | if (!m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) |
||
804 | m_cachedRegionMapItemsAddress.Add(regionhandle, httpserver); |
||
805 | } |
||
806 | } |
||
807 | else |
||
808 | { |
||
809 | lock (m_blacklistedregions) |
||
810 | { |
||
811 | if (!m_blacklistedregions.ContainsKey(regionhandle)) |
||
812 | m_blacklistedregions.Add(regionhandle, Environment.TickCount); |
||
813 | } |
||
814 | //m_log.InfoFormat("[WORLD MAP]: Blacklisted region {0}", regionhandle.ToString()); |
||
815 | } |
||
816 | } |
||
817 | |||
818 | blacklisted = false; |
||
819 | lock (m_blacklistedurls) |
||
820 | { |
||
821 | if (m_blacklistedurls.ContainsKey(httpserver)) |
||
822 | { |
||
823 | if (Environment.TickCount > (m_blacklistedurls[httpserver] + blacklistTimeout)) |
||
824 | { |
||
825 | m_log.DebugFormat("[WORLD MAP]: Unblock blacklisted URL {0}", httpserver); |
||
826 | |||
827 | m_blacklistedurls.Remove(httpserver); |
||
828 | } |
||
829 | else |
||
830 | blacklisted = true; |
||
831 | } |
||
832 | } |
||
833 | |||
834 | // Can't find the http server |
||
835 | if (httpserver.Length == 0 || blacklisted) |
||
836 | return new OSDMap(); |
||
837 | |||
838 | MapRequestState mrs = new MapRequestState(); |
||
839 | mrs.agentID = id; |
||
840 | mrs.EstateID = EstateID; |
||
841 | mrs.flags = flags; |
||
842 | mrs.godlike = godlike; |
||
843 | mrs.itemtype=itemtype; |
||
844 | mrs.regionhandle = regionhandle; |
||
845 | |||
846 | lock (m_openRequests) |
||
847 | m_openRequests.Add(requestID, mrs); |
||
848 | |||
849 | WebRequest mapitemsrequest = null; |
||
850 | try |
||
851 | { |
||
852 | mapitemsrequest = WebRequest.Create(httpserver); |
||
853 | } |
||
854 | catch (Exception e) |
||
855 | { |
||
856 | m_log.DebugFormat("[WORLD MAP]: Access to {0} failed with {1}", httpserver, e); |
||
857 | return new OSDMap(); |
||
858 | } |
||
859 | |||
860 | mapitemsrequest.Method = "POST"; |
||
861 | mapitemsrequest.ContentType = "application/xml+llsd"; |
||
862 | OSDMap RAMap = new OSDMap(); |
||
863 | |||
864 | // string RAMapString = RAMap.ToString(); |
||
865 | OSD LLSDofRAMap = RAMap; // RENAME if this works |
||
866 | |||
867 | byte[] buffer = OSDParser.SerializeLLSDXmlBytes(LLSDofRAMap); |
||
868 | OSDMap responseMap = new OSDMap(); |
||
869 | responseMap["requestID"] = OSD.FromUUID(requestID); |
||
870 | |||
871 | Stream os = null; |
||
872 | try |
||
873 | { // send the Post |
||
874 | mapitemsrequest.ContentLength = buffer.Length; //Count bytes to send |
||
875 | os = mapitemsrequest.GetRequestStream(); |
||
876 | os.Write(buffer, 0, buffer.Length); //Send it |
||
877 | //m_log.DebugFormat("[WORLD MAP]: Getting MapItems from {0}", httpserver); |
||
878 | } |
||
879 | catch (WebException ex) |
||
880 | { |
||
881 | m_log.WarnFormat("[WORLD MAP]: Bad send on GetMapItems {0}", ex.Message); |
||
882 | responseMap["connect"] = OSD.FromBoolean(false); |
||
883 | lock (m_blacklistedurls) |
||
884 | { |
||
885 | if (!m_blacklistedurls.ContainsKey(httpserver)) |
||
886 | m_blacklistedurls.Add(httpserver, Environment.TickCount); |
||
887 | } |
||
888 | |||
889 | m_log.WarnFormat("[WORLD MAP]: Blacklisted {0}", httpserver); |
||
890 | |||
891 | return responseMap; |
||
892 | } |
||
893 | catch |
||
894 | { |
||
895 | m_log.DebugFormat("[WORLD MAP]: RequestMapItems failed for {0}", httpserver); |
||
896 | responseMap["connect"] = OSD.FromBoolean(false); |
||
897 | return responseMap; |
||
898 | } |
||
899 | finally |
||
900 | { |
||
901 | if (os != null) |
||
902 | os.Dispose(); |
||
903 | } |
||
904 | |||
905 | string response_mapItems_reply = null; |
||
906 | { |
||
907 | try |
||
908 | { |
||
909 | using (WebResponse webResponse = mapitemsrequest.GetResponse()) |
||
910 | { |
||
911 | if (webResponse != null) |
||
912 | { |
||
913 | using (Stream s = webResponse.GetResponseStream()) |
||
914 | using (StreamReader sr = new StreamReader(s)) |
||
915 | response_mapItems_reply = sr.ReadToEnd().Trim(); |
||
916 | } |
||
917 | else |
||
918 | { |
||
919 | return new OSDMap(); |
||
920 | } |
||
921 | } |
||
922 | } |
||
923 | catch (WebException) |
||
924 | { |
||
925 | responseMap["connect"] = OSD.FromBoolean(false); |
||
926 | lock (m_blacklistedurls) |
||
927 | { |
||
928 | if (!m_blacklistedurls.ContainsKey(httpserver)) |
||
929 | m_blacklistedurls.Add(httpserver, Environment.TickCount); |
||
930 | } |
||
931 | |||
932 | m_log.WarnFormat("[WORLD MAP]: Blacklisted {0}", httpserver); |
||
933 | |||
934 | return responseMap; |
||
935 | } |
||
936 | catch |
||
937 | { |
||
938 | m_log.DebugFormat("[WORLD MAP]: RequestMapItems failed for {0}", httpserver); |
||
939 | responseMap["connect"] = OSD.FromBoolean(false); |
||
940 | lock (m_blacklistedregions) |
||
941 | { |
||
942 | if (!m_blacklistedregions.ContainsKey(regionhandle)) |
||
943 | m_blacklistedregions.Add(regionhandle, Environment.TickCount); |
||
944 | } |
||
945 | |||
946 | return responseMap; |
||
947 | } |
||
948 | |||
949 | OSD rezResponse = null; |
||
950 | try |
||
951 | { |
||
952 | rezResponse = OSDParser.DeserializeLLSDXml(response_mapItems_reply); |
||
953 | |||
954 | responseMap = (OSDMap)rezResponse; |
||
955 | responseMap["requestID"] = OSD.FromUUID(requestID); |
||
956 | } |
||
957 | catch (Exception ex) |
||
958 | { |
||
959 | m_log.InfoFormat("[WORLD MAP]: exception on parse of RequestMapItems reply from {0}: {1}", httpserver, ex.Message); |
||
960 | responseMap["connect"] = OSD.FromBoolean(false); |
||
961 | |||
962 | lock (m_blacklistedregions) |
||
963 | { |
||
964 | if (!m_blacklistedregions.ContainsKey(regionhandle)) |
||
965 | m_blacklistedregions.Add(regionhandle, Environment.TickCount); |
||
966 | } |
||
967 | |||
968 | return responseMap; |
||
969 | } |
||
970 | } |
||
971 | |||
972 | if (!responseMap.ContainsKey(itemtype.ToString())) // remote sim doesnt have the stated region handle |
||
973 | { |
||
974 | m_log.DebugFormat("[WORLD MAP]: Remote sim does not have the stated region. Blacklisting."); |
||
975 | lock (m_blacklistedregions) |
||
976 | { |
||
977 | if (!m_blacklistedregions.ContainsKey(regionhandle)) |
||
978 | m_blacklistedregions.Add(regionhandle, Environment.TickCount); |
||
979 | } |
||
980 | } |
||
981 | |||
982 | return responseMap; |
||
983 | } |
||
984 | |||
985 | /// <summary> |
||
986 | /// Requests map blocks in area of minX, maxX, minY, MaxY in world cordinates |
||
987 | /// </summary> |
||
988 | /// <param name="minX"></param> |
||
989 | /// <param name="minY"></param> |
||
990 | /// <param name="maxX"></param> |
||
991 | /// <param name="maxY"></param> |
||
992 | public virtual void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag) |
||
993 | { |
||
994 | if ((flag & 0x10000) != 0) // user clicked on qthe map a tile that isn't visible |
||
995 | { |
||
996 | List<MapBlockData> response = new List<MapBlockData>(); |
||
997 | |||
998 | // this should return one mapblock at most. It is triggered by a click |
||
999 | // on an unloaded square. |
||
1000 | // But make sure: Look whether the one we requested is in there |
||
1001 | List<GridRegion> regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, |
||
1002 | (int)Util.RegionToWorldLoc((uint)minX), (int)Util.RegionToWorldLoc((uint)maxX), |
||
1003 | (int)Util.RegionToWorldLoc((uint)minY), (int)Util.RegionToWorldLoc((uint)maxY) ); |
||
1004 | |||
1005 | m_log.DebugFormat("[WORLD MAP MODULE] RequestMapBlocks min=<{0},{1}>, max=<{2},{3}>, flag={4}, cntFound={5}", |
||
1006 | minX, minY, maxX, maxY, flag.ToString("X"), regions.Count); |
||
1007 | if (regions != null) |
||
1008 | { |
||
1009 | foreach (GridRegion r in regions) |
||
1010 | { |
||
1011 | if (r.RegionLocX == Util.RegionToWorldLoc((uint)minX) |
||
1012 | && r.RegionLocY == Util.RegionToWorldLoc((uint)minY) ) |
||
1013 | { |
||
1014 | // found it => add it to response |
||
1015 | // Version 2 viewers can handle the larger regions |
||
1016 | if ((flag & 2) == 2) |
||
1017 | response.AddRange(Map2BlockFromGridRegion(r, flag)); |
||
1018 | else |
||
1019 | response.Add(MapBlockFromGridRegion(r, flag)); |
||
1020 | break; |
||
1021 | } |
||
1022 | } |
||
1023 | } |
||
1024 | |||
1025 | if (response.Count == 0) |
||
1026 | { |
||
1027 | // response still empty => couldn't find the map-tile the user clicked on => tell the client |
||
1028 | MapBlockData block = new MapBlockData(); |
||
1029 | block.X = (ushort)minX; |
||
1030 | block.Y = (ushort)minY; |
||
1031 | block.Access = (byte)SimAccess.Down; // means 'simulator is offline' |
||
1032 | // block.Access = (byte)SimAccess.NonExistant; |
||
1033 | response.Add(block); |
||
1034 | } |
||
1035 | // The lower 16 bits are an unsigned int16 |
||
1036 | remoteClient.SendMapBlock(response, flag & 0xffff); |
||
1037 | } |
||
1038 | else |
||
1039 | { |
||
1040 | // normal mapblock request. Use the provided values |
||
1041 | GetAndSendBlocks(remoteClient, minX, minY, maxX, maxY, flag); |
||
1042 | } |
||
1043 | } |
||
1044 | |||
1045 | protected virtual List<MapBlockData> GetAndSendBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag) |
||
1046 | { |
||
1047 | List<MapBlockData> mapBlocks = new List<MapBlockData>(); |
||
1048 | List<GridRegion> regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, |
||
1049 | (int)Util.RegionToWorldLoc((uint)(minX - 4)), (int)Util.RegionToWorldLoc((uint)(maxX + 4)), |
||
1050 | (int)Util.RegionToWorldLoc((uint)(minY - 4)), (int)Util.RegionToWorldLoc((uint)(maxY + 4)) ); |
||
1051 | m_log.DebugFormat("{0} GetAndSendBlocks. min=<{1},{2}>, max=<{3},{4}>, cntFound={5}", |
||
1052 | LogHeader, minX, minY, maxX, maxY, regions.Count); |
||
1053 | foreach (GridRegion r in regions) |
||
1054 | { |
||
1055 | // Version 2 viewers can handle the larger regions |
||
1056 | if ((flag & 2) == 2) |
||
1057 | mapBlocks.AddRange(Map2BlockFromGridRegion(r, flag)); |
||
1058 | else |
||
1059 | mapBlocks.Add(MapBlockFromGridRegion(r, flag)); |
||
1060 | } |
||
1061 | remoteClient.SendMapBlock(mapBlocks, flag & 0xffff); |
||
1062 | |||
1063 | return mapBlocks; |
||
1064 | } |
||
1065 | |||
1066 | // Fill a passed MapBlockData from a GridRegion |
||
1067 | protected MapBlockData MapBlockFromGridRegion(GridRegion r, uint flag) |
||
1068 | { |
||
1069 | MapBlockData block = new MapBlockData(); |
||
1070 | |||
1071 | block.Access = r.Access; |
||
1072 | switch (flag & 0xffff) |
||
1073 | { |
||
1074 | case 0: |
||
1075 | block.MapImageId = r.TerrainImage; |
||
1076 | break; |
||
1077 | case 2: |
||
1078 | block.MapImageId = r.ParcelImage; |
||
1079 | break; |
||
1080 | default: |
||
1081 | block.MapImageId = UUID.Zero; |
||
1082 | break; |
||
1083 | } |
||
1084 | block.Name = r.RegionName; |
||
1085 | block.X = (ushort)Util.WorldToRegionLoc((uint)r.RegionLocX); |
||
1086 | block.Y = (ushort)Util.WorldToRegionLoc((uint)r.RegionLocY); |
||
1087 | block.SizeX = (ushort) r.RegionSizeX; |
||
1088 | block.SizeY = (ushort) r.RegionSizeY; |
||
1089 | |||
1090 | return block; |
||
1091 | } |
||
1092 | |||
1093 | protected List<MapBlockData> Map2BlockFromGridRegion(GridRegion r, uint flag) |
||
1094 | { |
||
1095 | List<MapBlockData> blocks = new List<MapBlockData>(); |
||
1096 | MapBlockData block = new MapBlockData(); |
||
1097 | if (r == null) |
||
1098 | { |
||
1099 | block.Access = (byte)SimAccess.Down; |
||
1100 | block.MapImageId = UUID.Zero; |
||
1101 | blocks.Add(block); |
||
1102 | } |
||
1103 | else |
||
1104 | { |
||
1105 | block.Access = r.Access; |
||
1106 | switch (flag & 0xffff) |
||
1107 | { |
||
1108 | case 0: |
||
1109 | block.MapImageId = r.TerrainImage; |
||
1110 | break; |
||
1111 | case 2: |
||
1112 | block.MapImageId = r.ParcelImage; |
||
1113 | break; |
||
1114 | default: |
||
1115 | block.MapImageId = UUID.Zero; |
||
1116 | break; |
||
1117 | } |
||
1118 | block.Name = r.RegionName; |
||
1119 | block.X = (ushort)(r.RegionLocX / Constants.RegionSize); |
||
1120 | block.Y = (ushort)(r.RegionLocY / Constants.RegionSize); |
||
1121 | block.SizeX = (ushort)r.RegionSizeX; |
||
1122 | block.SizeY = (ushort)r.RegionSizeY; |
||
1123 | blocks.Add(block); |
||
1124 | // If these are larger than legacy regions, create fake map entries for the covered |
||
1125 | // regions. The map system only does legacy sized regions so we have to fake map |
||
1126 | // entries for all the covered regions. |
||
1127 | if (r.RegionSizeX > Constants.RegionSize || r.RegionSizeY > Constants.RegionSize) |
||
1128 | { |
||
1129 | for (int x = 0; x < r.RegionSizeX / Constants.RegionSize; x++) |
||
1130 | { |
||
1131 | for (int y = 0; y < r.RegionSizeY / Constants.RegionSize; y++) |
||
1132 | { |
||
1133 | if (x == 0 && y == 0) |
||
1134 | continue; |
||
1135 | block = new MapBlockData |
||
1136 | { |
||
1137 | Access = r.Access, |
||
1138 | MapImageId = r.TerrainImage, |
||
1139 | Name = r.RegionName, |
||
1140 | X = (ushort)((r.RegionLocX / Constants.RegionSize) + x), |
||
1141 | Y = (ushort)((r.RegionLocY / Constants.RegionSize) + y), |
||
1142 | SizeX = (ushort)r.RegionSizeX, |
||
1143 | SizeY = (ushort)r.RegionSizeY |
||
1144 | }; |
||
1145 | //Child piece, so ignore it |
||
1146 | blocks.Add(block); |
||
1147 | } |
||
1148 | } |
||
1149 | } |
||
1150 | } |
||
1151 | return blocks; |
||
1152 | } |
||
1153 | |||
1154 | |||
1155 | public Hashtable OnHTTPThrottled(Hashtable keysvals) |
||
1156 | { |
||
1157 | Hashtable reply = new Hashtable(); |
||
1158 | int statuscode = 500; |
||
1159 | reply["str_response_string"] = ""; |
||
1160 | reply["int_response_code"] = statuscode; |
||
1161 | reply["content_type"] = "text/plain"; |
||
1162 | return reply; |
||
1163 | } |
||
1164 | |||
1165 | public Hashtable OnHTTPGetMapImage(Hashtable keysvals) |
||
1166 | { |
||
1167 | m_log.Debug("[WORLD MAP]: Sending map image jpeg"); |
||
1168 | Hashtable reply = new Hashtable(); |
||
1169 | int statuscode = 200; |
||
1170 | byte[] jpeg = new byte[0]; |
||
1171 | |||
1172 | if (myMapImageJPEG.Length == 0) |
||
1173 | { |
||
1174 | MemoryStream imgstream = new MemoryStream(); |
||
1175 | Bitmap mapTexture = new Bitmap(1,1); |
||
1176 | ManagedImage managedImage; |
||
1177 | Image image = (Image)mapTexture; |
||
1178 | |||
1179 | try |
||
1180 | { |
||
1181 | // Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular jpeg data |
||
1182 | |||
1183 | imgstream = new MemoryStream(); |
||
1184 | |||
1185 | // non-async because we know we have the asset immediately. |
||
1186 | AssetBase mapasset = m_scene.AssetService.Get(m_scene.RegionInfo.RegionSettings.TerrainImageID.ToString()); |
||
1187 | |||
1188 | // Decode image to System.Drawing.Image |
||
1189 | if (OpenJPEG.DecodeToImage(mapasset.Data, out managedImage, out image)) |
||
1190 | { |
||
1191 | // Save to bitmap |
||
1192 | mapTexture = new Bitmap(image); |
||
1193 | |||
1194 | EncoderParameters myEncoderParameters = new EncoderParameters(); |
||
1195 | myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L); |
||
1196 | |||
1197 | // Save bitmap to stream |
||
1198 | mapTexture.Save(imgstream, GetEncoderInfo("image/jpeg"), myEncoderParameters); |
||
1199 | |||
1200 | // Write the stream to a byte array for output |
||
1201 | jpeg = imgstream.ToArray(); |
||
1202 | myMapImageJPEG = jpeg; |
||
1203 | } |
||
1204 | } |
||
1205 | catch (Exception) |
||
1206 | { |
||
1207 | // Dummy! |
||
1208 | m_log.Warn("[WORLD MAP]: Unable to generate Map image"); |
||
1209 | } |
||
1210 | finally |
||
1211 | { |
||
1212 | // Reclaim memory, these are unmanaged resources |
||
1213 | // If we encountered an exception, one or more of these will be null |
||
1214 | if (mapTexture != null) |
||
1215 | mapTexture.Dispose(); |
||
1216 | |||
1217 | if (image != null) |
||
1218 | image.Dispose(); |
||
1219 | |||
1220 | if (imgstream != null) |
||
1221 | { |
||
1222 | imgstream.Close(); |
||
1223 | imgstream.Dispose(); |
||
1224 | } |
||
1225 | } |
||
1226 | } |
||
1227 | else |
||
1228 | { |
||
1229 | // Use cached version so we don't have to loose our mind |
||
1230 | jpeg = myMapImageJPEG; |
||
1231 | } |
||
1232 | |||
1233 | reply["str_response_string"] = Convert.ToBase64String(jpeg); |
||
1234 | reply["int_response_code"] = statuscode; |
||
1235 | reply["content_type"] = "image/jpeg"; |
||
1236 | |||
1237 | return reply; |
||
1238 | } |
||
1239 | |||
1240 | // From msdn |
||
1241 | private static ImageCodecInfo GetEncoderInfo(String mimeType) |
||
1242 | { |
||
1243 | ImageCodecInfo[] encoders; |
||
1244 | encoders = ImageCodecInfo.GetImageEncoders(); |
||
1245 | for (int j = 0; j < encoders.Length; ++j) |
||
1246 | { |
||
1247 | if (encoders[j].MimeType == mimeType) |
||
1248 | return encoders[j]; |
||
1249 | } |
||
1250 | return null; |
||
1251 | } |
||
1252 | |||
1253 | /// <summary> |
||
1254 | /// Export the world map |
||
1255 | /// </summary> |
||
1256 | /// <param name="fileName"></param> |
||
1257 | public void HandleExportWorldMapConsoleCommand(string module, string[] cmdparams) |
||
1258 | { |
||
1259 | if (m_scene.ConsoleScene() == null) |
||
1260 | { |
||
1261 | // FIXME: If console region is root then this will be printed by every module. Currently, there is no |
||
1262 | // way to prevent this, short of making the entire module shared (which is complete overkill). |
||
1263 | // One possibility is to return a bool to signal whether the module has completely handled the command |
||
1264 | m_log.InfoFormat("[WORLD MAP]: Please change to a specific region in order to export its world map"); |
||
1265 | return; |
||
1266 | } |
||
1267 | |||
1268 | if (m_scene.ConsoleScene() != m_scene) |
||
1269 | return; |
||
1270 | |||
1271 | string exportPath; |
||
1272 | |||
1273 | if (cmdparams.Length > 1) |
||
1274 | exportPath = cmdparams[1]; |
||
1275 | else |
||
1276 | exportPath = DEFAULT_WORLD_MAP_EXPORT_PATH; |
||
1277 | |||
1278 | m_log.InfoFormat( |
||
1279 | "[WORLD MAP]: Exporting world map for {0} to {1}", m_scene.RegionInfo.RegionName, exportPath); |
||
1280 | |||
1281 | List<MapBlockData> mapBlocks = new List<MapBlockData>(); |
||
1282 | List<GridRegion> regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, |
||
1283 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX - 9), |
||
1284 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocX + 9), |
||
1285 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY - 9), |
||
1286 | (int)Util.RegionToWorldLoc(m_scene.RegionInfo.RegionLocY + 9)); |
||
1287 | List<AssetBase> textures = new List<AssetBase>(); |
||
1288 | List<Image> bitImages = new List<Image>(); |
||
1289 | |||
1290 | foreach (GridRegion r in regions) |
||
1291 | { |
||
1292 | MapBlockData mapBlock = MapBlockFromGridRegion(r, 0); |
||
1293 | AssetBase texAsset = m_scene.AssetService.Get(mapBlock.MapImageId.ToString()); |
||
1294 | |||
1295 | if (texAsset != null) |
||
1296 | { |
||
1297 | textures.Add(texAsset); |
||
1298 | } |
||
1299 | //else |
||
1300 | //{ |
||
1301 | // // WHAT?!? This doesn't seem right. Commenting (diva) |
||
1302 | // texAsset = m_scene.AssetService.Get(mapBlock.MapImageId.ToString()); |
||
1303 | // if (texAsset != null) |
||
1304 | // { |
||
1305 | // textures.Add(texAsset); |
||
1306 | // } |
||
1307 | //} |
||
1308 | } |
||
1309 | |||
1310 | foreach (AssetBase asset in textures) |
||
1311 | { |
||
1312 | ManagedImage managedImage; |
||
1313 | Image image; |
||
1314 | |||
1315 | if (OpenJPEG.DecodeToImage(asset.Data, out managedImage, out image)) |
||
1316 | bitImages.Add(image); |
||
1317 | } |
||
1318 | |||
1319 | Bitmap mapTexture = new Bitmap(2560, 2560); |
||
1320 | Graphics g = Graphics.FromImage(mapTexture); |
||
1321 | SolidBrush sea = new SolidBrush(Color.DarkBlue); |
||
1322 | g.FillRectangle(sea, 0, 0, 2560, 2560); |
||
1323 | |||
1324 | for (int i = 0; i < mapBlocks.Count; i++) |
||
1325 | { |
||
1326 | ushort x = (ushort)((mapBlocks[i].X - m_scene.RegionInfo.RegionLocX) + 10); |
||
1327 | ushort y = (ushort)((mapBlocks[i].Y - m_scene.RegionInfo.RegionLocY) + 10); |
||
1328 | g.DrawImage(bitImages[i], (x * 128), 2560 - (y * 128), 128, 128); // y origin is top |
||
1329 | } |
||
1330 | |||
1331 | mapTexture.Save(exportPath, ImageFormat.Jpeg); |
||
1332 | |||
1333 | m_log.InfoFormat( |
||
1334 | "[WORLD MAP]: Successfully exported world map for {0} to {1}", |
||
1335 | m_scene.RegionInfo.RegionName, exportPath); |
||
1336 | } |
||
1337 | |||
1338 | public void HandleGenerateMapConsoleCommand(string module, string[] cmdparams) |
||
1339 | { |
||
1340 | Scene consoleScene = m_scene.ConsoleScene(); |
||
1341 | |||
1342 | if (consoleScene != null && consoleScene != m_scene) |
||
1343 | return; |
||
1344 | |||
1345 | GenerateMaptile(); |
||
1346 | } |
||
1347 | |||
1348 | public OSD HandleRemoteMapItemRequest(string path, OSD request, string endpoint) |
||
1349 | { |
||
1350 | uint xstart = 0; |
||
1351 | uint ystart = 0; |
||
1352 | |||
1353 | Util.RegionHandleToWorldLoc(m_scene.RegionInfo.RegionHandle,out xstart,out ystart); |
||
1354 | // m_log.DebugFormat("{0} HandleRemoteMapItemRequest. loc=<{1},{2}>", |
||
1355 | // LogHeader, Util.WorldToRegionLoc(xstart), Util.WorldToRegionLoc(ystart)); |
||
1356 | |||
1357 | // Service 6 (MAP_ITEM_AGENTS_LOCATION; green dots) |
||
1358 | |||
1359 | OSDMap responsemap = new OSDMap(); |
||
1360 | int tc = Environment.TickCount; |
||
1361 | if (m_scene.GetRootAgentCount() == 0) |
||
1362 | { |
||
1363 | OSDMap responsemapdata = new OSDMap(); |
||
1364 | responsemapdata["X"] = OSD.FromInteger((int)(xstart + 1)); |
||
1365 | responsemapdata["Y"] = OSD.FromInteger((int)(ystart + 1)); |
||
1366 | responsemapdata["ID"] = OSD.FromUUID(UUID.Zero); |
||
1367 | responsemapdata["Name"] = OSD.FromString(Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString())); |
||
1368 | responsemapdata["Extra"] = OSD.FromInteger(0); |
||
1369 | responsemapdata["Extra2"] = OSD.FromInteger(0); |
||
1370 | OSDArray responsearr = new OSDArray(); |
||
1371 | responsearr.Add(responsemapdata); |
||
1372 | |||
1373 | responsemap["6"] = responsearr; |
||
1374 | } |
||
1375 | else |
||
1376 | { |
||
1377 | OSDArray responsearr = new OSDArray(m_scene.GetRootAgentCount()); |
||
1378 | m_scene.ForEachRootScenePresence(delegate(ScenePresence sp) |
||
1379 | { |
||
1380 | OSDMap responsemapdata = new OSDMap(); |
||
1381 | responsemapdata["X"] = OSD.FromInteger((int)(xstart + sp.AbsolutePosition.X)); |
||
1382 | responsemapdata["Y"] = OSD.FromInteger((int)(ystart + sp.AbsolutePosition.Y)); |
||
1383 | responsemapdata["ID"] = OSD.FromUUID(UUID.Zero); |
||
1384 | responsemapdata["Name"] = OSD.FromString(Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString())); |
||
1385 | responsemapdata["Extra"] = OSD.FromInteger(1); |
||
1386 | responsemapdata["Extra2"] = OSD.FromInteger(0); |
||
1387 | responsearr.Add(responsemapdata); |
||
1388 | }); |
||
1389 | responsemap["6"] = responsearr; |
||
1390 | } |
||
1391 | |||
1392 | // Service 7 (MAP_ITEM_LAND_FOR_SALE) |
||
1393 | |||
1394 | ILandChannel landChannel = m_scene.LandChannel; |
||
1395 | List<ILandObject> parcels = landChannel.AllParcels(); |
||
1396 | |||
1397 | if ((parcels == null) || (parcels.Count == 0)) |
||
1398 | { |
||
1399 | OSDMap responsemapdata = new OSDMap(); |
||
1400 | responsemapdata["X"] = OSD.FromInteger((int)(xstart + 1)); |
||
1401 | responsemapdata["Y"] = OSD.FromInteger((int)(ystart + 1)); |
||
1402 | responsemapdata["ID"] = OSD.FromUUID(UUID.Zero); |
||
1403 | responsemapdata["Name"] = OSD.FromString(""); |
||
1404 | responsemapdata["Extra"] = OSD.FromInteger(0); |
||
1405 | responsemapdata["Extra2"] = OSD.FromInteger(0); |
||
1406 | OSDArray responsearr = new OSDArray(); |
||
1407 | responsearr.Add(responsemapdata); |
||
1408 | |||
1409 | responsemap["7"] = responsearr; |
||
1410 | } |
||
1411 | else |
||
1412 | { |
||
1413 | OSDArray responsearr = new OSDArray(m_scene.GetRootAgentCount()); |
||
1414 | foreach (ILandObject parcel_interface in parcels) |
||
1415 | { |
||
1416 | // Play it safe |
||
1417 | if (!(parcel_interface is LandObject)) |
||
1418 | continue; |
||
1419 | |||
1420 | LandObject land = (LandObject)parcel_interface; |
||
1421 | LandData parcel = land.LandData; |
||
1422 | |||
1423 | // Show land for sale |
||
1424 | if ((parcel.Flags & (uint)ParcelFlags.ForSale) == (uint)ParcelFlags.ForSale) |
||
1425 | { |
||
1426 | Vector3 min = parcel.AABBMin; |
||
1427 | Vector3 max = parcel.AABBMax; |
||
1428 | float x = (min.X+max.X)/2; |
||
1429 | float y = (min.Y+max.Y)/2; |
||
1430 | |||
1431 | OSDMap responsemapdata = new OSDMap(); |
||
1432 | responsemapdata["X"] = OSD.FromInteger((int)(xstart + x)); |
||
1433 | responsemapdata["Y"] = OSD.FromInteger((int)(ystart + y)); |
||
1434 | // responsemapdata["Z"] = OSD.FromInteger((int)m_scene.GetGroundHeight(x,y)); |
||
1435 | responsemapdata["ID"] = OSD.FromUUID(parcel.GlobalID); |
||
1436 | responsemapdata["Name"] = OSD.FromString(parcel.Name); |
||
1437 | responsemapdata["Extra"] = OSD.FromInteger(parcel.Area); |
||
1438 | responsemapdata["Extra2"] = OSD.FromInteger(parcel.SalePrice); |
||
1439 | responsearr.Add(responsemapdata); |
||
1440 | } |
||
1441 | } |
||
1442 | responsemap["7"] = responsearr; |
||
1443 | } |
||
1444 | |||
1445 | if (m_scene.RegionInfo.RegionSettings.TelehubObject != UUID.Zero) |
||
1446 | { |
||
1447 | SceneObjectGroup sog = m_scene.GetSceneObjectGroup(m_scene.RegionInfo.RegionSettings.TelehubObject); |
||
1448 | if (sog != null) |
||
1449 | { |
||
1450 | OSDArray responsearr = new OSDArray(); |
||
1451 | OSDMap responsemapdata = new OSDMap(); |
||
1452 | responsemapdata["X"] = OSD.FromInteger((int)(xstart + sog.AbsolutePosition.X)); |
||
1453 | responsemapdata["Y"] = OSD.FromInteger((int)(ystart + sog.AbsolutePosition.Y)); |
||
1454 | // responsemapdata["Z"] = OSD.FromInteger((int)m_scene.GetGroundHeight(x,y)); |
||
1455 | responsemapdata["ID"] = OSD.FromUUID(sog.UUID); |
||
1456 | responsemapdata["Name"] = OSD.FromString(sog.Name); |
||
1457 | responsemapdata["Extra"] = OSD.FromInteger(0); // color (unused) |
||
1458 | responsemapdata["Extra2"] = OSD.FromInteger(0); // 0 = telehub / 1 = infohub |
||
1459 | responsearr.Add(responsemapdata); |
||
1460 | |||
1461 | responsemap["1"] = responsearr; |
||
1462 | } |
||
1463 | } |
||
1464 | |||
1465 | return responsemap; |
||
1466 | } |
||
1467 | |||
1468 | public void GenerateMaptile() |
||
1469 | { |
||
1470 | // Cannot create a map for a nonexistant heightmap |
||
1471 | if (m_scene.Heightmap == null) |
||
1472 | return; |
||
1473 | |||
1474 | //create a texture asset of the terrain |
||
1475 | IMapImageGenerator terrain = m_scene.RequestModuleInterface<IMapImageGenerator>(); |
||
1476 | if (terrain == null) |
||
1477 | return; |
||
1478 | |||
1479 | m_log.DebugFormat("[WORLD MAP]: Generating map image for {0}", m_scene.RegionInfo.RegionName); |
||
1480 | |||
1481 | byte[] data = terrain.WriteJpeg2000Image(); |
||
1482 | if (data == null) |
||
1483 | return; |
||
1484 | |||
1485 | byte[] overlay = GenerateOverlay(); |
||
1486 | |||
1487 | UUID terrainImageID = UUID.Random(); |
||
1488 | UUID parcelImageID = UUID.Zero; |
||
1489 | |||
1490 | AssetBase asset = new AssetBase( |
||
1491 | terrainImageID, |
||
1492 | "terrainImage_" + m_scene.RegionInfo.RegionID.ToString(), |
||
1493 | (sbyte)AssetType.Texture, |
||
1494 | m_scene.RegionInfo.RegionID.ToString()); |
||
1495 | asset.Data = data; |
||
1496 | asset.Description = m_scene.RegionInfo.RegionName; |
||
1497 | asset.Temporary = false; |
||
1498 | asset.Flags = AssetFlags.Maptile; |
||
1499 | |||
1500 | // Store the new one |
||
1501 | m_log.DebugFormat("[WORLD MAP]: Storing map tile {0} for {1}", asset.ID, m_scene.RegionInfo.RegionName); |
||
1502 | |||
1503 | m_scene.AssetService.Store(asset); |
||
1504 | |||
1505 | if (overlay != null) |
||
1506 | { |
||
1507 | parcelImageID = UUID.Random(); |
||
1508 | |||
1509 | AssetBase parcels = new AssetBase( |
||
1510 | parcelImageID, |
||
1511 | "parcelImage_" + m_scene.RegionInfo.RegionID.ToString(), |
||
1512 | (sbyte)AssetType.Texture, |
||
1513 | m_scene.RegionInfo.RegionID.ToString()); |
||
1514 | parcels.Data = overlay; |
||
1515 | parcels.Description = m_scene.RegionInfo.RegionName; |
||
1516 | parcels.Temporary = false; |
||
1517 | parcels.Flags = AssetFlags.Maptile; |
||
1518 | |||
1519 | m_scene.AssetService.Store(parcels); |
||
1520 | } |
||
1521 | |||
1522 | // Switch to the new one |
||
1523 | UUID lastTerrainImageID = m_scene.RegionInfo.RegionSettings.TerrainImageID; |
||
1524 | UUID lastParcelImageID = m_scene.RegionInfo.RegionSettings.ParcelImageID; |
||
1525 | m_scene.RegionInfo.RegionSettings.TerrainImageID = terrainImageID; |
||
1526 | m_scene.RegionInfo.RegionSettings.ParcelImageID = parcelImageID; |
||
1527 | m_scene.RegionInfo.RegionSettings.Save(); |
||
1528 | |||
1529 | // Delete the old one |
||
1530 | // m_log.DebugFormat("[WORLDMAP]: Deleting old map tile {0}", lastTerrainImageID); |
||
1531 | m_scene.AssetService.Delete(lastTerrainImageID.ToString()); |
||
1532 | if (lastParcelImageID != UUID.Zero) |
||
1533 | m_scene.AssetService.Delete(lastParcelImageID.ToString()); |
||
1534 | } |
||
1535 | |||
1536 | private void MakeRootAgent(ScenePresence avatar) |
||
1537 | { |
||
1538 | lock (m_rootAgents) |
||
1539 | { |
||
1540 | if (!m_rootAgents.Contains(avatar.UUID)) |
||
1541 | { |
||
1542 | m_rootAgents.Add(avatar.UUID); |
||
1543 | } |
||
1544 | } |
||
1545 | } |
||
1546 | |||
1547 | private void MakeChildAgent(ScenePresence avatar) |
||
1548 | { |
||
1549 | lock (m_rootAgents) |
||
1550 | { |
||
1551 | m_rootAgents.Remove(avatar.UUID); |
||
1552 | } |
||
1553 | } |
||
1554 | |||
1555 | public void OnRegionUp(GridRegion otherRegion) |
||
1556 | { |
||
1557 | ulong regionhandle = otherRegion.RegionHandle; |
||
1558 | string httpserver = otherRegion.ServerURI + "MAP/MapItems/" + regionhandle.ToString(); |
||
1559 | |||
1560 | lock (m_blacklistedregions) |
||
1561 | { |
||
1562 | if (!m_blacklistedregions.ContainsKey(regionhandle)) |
||
1563 | m_blacklistedregions.Remove(regionhandle); |
||
1564 | } |
||
1565 | |||
1566 | lock (m_blacklistedurls) |
||
1567 | { |
||
1568 | if (m_blacklistedurls.ContainsKey(httpserver)) |
||
1569 | m_blacklistedurls.Remove(httpserver); |
||
1570 | } |
||
1571 | |||
1572 | lock (m_cachedRegionMapItemsAddress) |
||
1573 | { |
||
1574 | if (!m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) |
||
1575 | m_cachedRegionMapItemsAddress.Remove(regionhandle); |
||
1576 | } |
||
1577 | } |
||
1578 | |||
1579 | private Byte[] GenerateOverlay() |
||
1580 | { |
||
1581 | using (Bitmap overlay = new Bitmap(256, 256)) |
||
1582 | { |
||
1583 | bool[,] saleBitmap = new bool[64, 64]; |
||
1584 | for (int x = 0 ; x < 64 ; x++) |
||
1585 | { |
||
1586 | for (int y = 0 ; y < 64 ; y++) |
||
1587 | saleBitmap[x, y] = false; |
||
1588 | } |
||
1589 | |||
1590 | bool landForSale = false; |
||
1591 | |||
1592 | List<ILandObject> parcels = m_scene.LandChannel.AllParcels(); |
||
1593 | |||
1594 | Color background = Color.FromArgb(0, 0, 0, 0); |
||
1595 | |||
1596 | using (Graphics g = Graphics.FromImage(overlay)) |
||
1597 | { |
||
1598 | using (SolidBrush transparent = new SolidBrush(background)) |
||
1599 | g.FillRectangle(transparent, 0, 0, 256, 256); |
||
1600 | |||
1601 | |||
1602 | foreach (ILandObject land in parcels) |
||
1603 | { |
||
1604 | // m_log.DebugFormat("[WORLD MAP]: Parcel {0} flags {1}", land.LandData.Name, land.LandData.Flags); |
||
1605 | if ((land.LandData.Flags & (uint)ParcelFlags.ForSale) != 0) |
||
1606 | { |
||
1607 | landForSale = true; |
||
1608 | |||
1609 | saleBitmap = land.MergeLandBitmaps(saleBitmap, land.GetLandBitmap()); |
||
1610 | } |
||
1611 | } |
||
1612 | |||
1613 | if (!landForSale) |
||
1614 | { |
||
1615 | m_log.DebugFormat("[WORLD MAP]: Region {0} has no parcels for sale, not generating overlay", m_scene.RegionInfo.RegionName); |
||
1616 | return null; |
||
1617 | } |
||
1618 | |||
1619 | m_log.DebugFormat("[WORLD MAP]: Region {0} has parcels for sale, generating overlay", m_scene.RegionInfo.RegionName); |
||
1620 | |||
1621 | using (SolidBrush yellow = new SolidBrush(Color.FromArgb(255, 249, 223, 9))) |
||
1622 | { |
||
1623 | for (int x = 0 ; x < 64 ; x++) |
||
1624 | { |
||
1625 | for (int y = 0 ; y < 64 ; y++) |
||
1626 | { |
||
1627 | if (saleBitmap[x, y]) |
||
1628 | g.FillRectangle(yellow, x * 4, 252 - (y * 4), 4, 4); |
||
1629 | } |
||
1630 | } |
||
1631 | } |
||
1632 | } |
||
1633 | |||
1634 | try |
||
1635 | { |
||
1636 | return OpenJPEG.EncodeFromImage(overlay, true); |
||
1637 | } |
||
1638 | catch (Exception e) |
||
1639 | { |
||
1640 | m_log.DebugFormat("[WORLD MAP]: Error creating parcel overlay: " + e.ToString()); |
||
1641 | } |
||
1642 | } |
||
1643 | |||
1644 | return null; |
||
1645 | } |
||
1646 | } |
||
1647 | |||
1648 | public struct MapRequestState |
||
1649 | { |
||
1650 | public UUID agentID; |
||
1651 | public uint flags; |
||
1652 | public uint EstateID; |
||
1653 | public bool godlike; |
||
1654 | public uint itemtype; |
||
1655 | public ulong regionhandle; |
||
1656 | } |
||
1657 | } |