aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs')
-rw-r--r--OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs1347
1 files changed, 1347 insertions, 0 deletions
diff --git a/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs
new file mode 100644
index 0000000..6ae6576
--- /dev/null
+++ b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs
@@ -0,0 +1,1347 @@
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 OpenSim 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
28using System;
29using System.Collections;
30using System.Collections.Generic;
31using System.Reflection;
32using OpenMetaverse;
33using log4net;
34using Nini.Config;
35using OpenSim.Region.Framework.Interfaces;
36using OpenSim.Region.Framework.Scenes;
37using OpenSim.Framework;
38using OpenSim.Framework.Servers;
39using OpenSim.Framework.Communications.Capabilities;
40using OpenSim.Region.Physics.Manager;
41using Caps = OpenSim.Framework.Communications.Capabilities.Caps;
42
43namespace OpenSim.Region.CoreModules.World.Land
44{
45 // used for caching
46 internal class ExtendedLandData {
47 public LandData landData;
48 public ulong regionHandle;
49 public uint x, y;
50 }
51
52 public class LandManagementModule : IRegionModule
53 {
54 private static readonly ILog m_log =
55 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
56
57 private static readonly string remoteParcelRequestPath = "0009/";
58
59 private LandChannel landChannel;
60 private Scene m_scene;
61
62 private readonly int[,] m_landIDList = new int[64, 64];
63 private readonly Dictionary<int, ILandObject> m_landList = new Dictionary<int, ILandObject>();
64
65 private bool m_landPrimCountTainted;
66 private int m_lastLandLocalID = LandChannel.START_LAND_LOCAL_ID - 1;
67
68 private bool m_allowedForcefulBans = true;
69
70 // caches ExtendedLandData
71 private Cache parcelInfoCache;
72
73 #region IRegionModule Members
74
75 public void Initialise(Scene scene, IConfigSource source)
76 {
77 m_scene = scene;
78 m_landIDList.Initialize();
79 landChannel = new LandChannel(scene, this);
80
81 parcelInfoCache = new Cache();
82 parcelInfoCache.Size = 30; // the number of different parcel requests in this region to cache
83 parcelInfoCache.DefaultTTL = new TimeSpan(0, 5, 0);
84
85 m_scene.EventManager.OnParcelPrimCountAdd += AddPrimToLandPrimCounts;
86 m_scene.EventManager.OnParcelPrimCountUpdate += UpdateLandPrimCounts;
87 m_scene.EventManager.OnAvatarEnteringNewParcel += new EventManager.AvatarEnteringNewParcel(handleAvatarChangingParcel);
88 m_scene.EventManager.OnClientMovement += new EventManager.ClientMovement(handleAnyClientMovement);
89 m_scene.EventManager.OnValidateLandBuy += handleLandValidationRequest;
90 m_scene.EventManager.OnLandBuy += handleLandBuyRequest;
91 m_scene.EventManager.OnNewClient += new EventManager.OnNewClientDelegate(EventManager_OnNewClient);
92 m_scene.EventManager.OnSignificantClientMovement += handleSignificantClientMovement;
93 m_scene.EventManager.OnObjectBeingRemovedFromScene += RemovePrimFromLandPrimCounts;
94
95 m_scene.EventManager.OnNoticeNoLandDataFromStorage += this.NoLandDataFromStorage;
96 m_scene.EventManager.OnIncomingLandDataFromStorage += this.IncomingLandObjectsFromStorage;
97 m_scene.EventManager.OnSetAllowForcefulBan += this.SetAllowedForcefulBans;
98 m_scene.EventManager.OnRequestParcelPrimCountUpdate += this.PerformParcelPrimCountUpdate;
99 m_scene.EventManager.OnParcelPrimCountTainted += this.SetPrimsTainted;
100 m_scene.EventManager.OnRegisterCaps += this.OnRegisterCaps;
101
102 lock (m_scene)
103 {
104 m_scene.LandChannel = (ILandChannel) landChannel;
105 }
106 }
107
108 void EventManager_OnNewClient(IClientAPI client)
109 {
110 //Register some client events
111 client.OnParcelPropertiesRequest += new ParcelPropertiesRequest(handleParcelPropertiesRequest);
112 client.OnParcelDivideRequest += new ParcelDivideRequest(handleParcelDivideRequest);
113 client.OnParcelJoinRequest += new ParcelJoinRequest(handleParcelJoinRequest);
114 client.OnParcelPropertiesUpdateRequest += new ParcelPropertiesUpdateRequest(handleParcelPropertiesUpdateRequest);
115 client.OnParcelSelectObjects += new ParcelSelectObjects(handleParcelSelectObjectsRequest);
116 client.OnParcelObjectOwnerRequest += new ParcelObjectOwnerRequest(handleParcelObjectOwnersRequest);
117 client.OnParcelAccessListRequest += new ParcelAccessListRequest(handleParcelAccessRequest);
118 client.OnParcelAccessListUpdateRequest += new ParcelAccessListUpdateRequest(handleParcelAccessUpdateRequest);
119 client.OnParcelAbandonRequest += new ParcelAbandonRequest(handleParcelAbandonRequest);
120 client.OnParcelGodForceOwner += new ParcelGodForceOwner(handleParcelGodForceOwner);
121 client.OnParcelReclaim += new ParcelReclaim(handleParcelReclaim);
122 client.OnParcelInfoRequest += new ParcelInfoRequest(handleParcelInfo);
123 client.OnParcelDwellRequest += new ParcelDwellRequest(handleParcelDwell);
124 if (m_scene.Entities.ContainsKey(client.AgentId))
125 {
126 SendLandUpdate((ScenePresence)m_scene.Entities[client.AgentId], true);
127 SendParcelOverlay(client);
128 }
129 }
130
131 public void PostInitialise()
132 {
133 }
134
135 public void Close()
136 {
137 }
138
139 public string Name
140 {
141 get { return "LandManagementModule"; }
142 }
143
144 public bool IsSharedModule
145 {
146 get { return false; }
147 }
148
149 #endregion
150
151 #region Parcel Add/Remove/Get/Create
152
153 public void SetAllowedForcefulBans(bool forceful)
154 {
155 AllowedForcefulBans = forceful;
156 }
157
158 public void UpdateLandObject(int local_id, LandData data)
159 {
160 LandData newData = data.Copy();
161 newData.LocalID = local_id;
162
163 lock (m_landList)
164 {
165 if (m_landList.ContainsKey(local_id))
166 {
167 m_landList[local_id].landData = newData;
168 m_scene.EventManager.TriggerLandObjectUpdated((uint)local_id, m_landList[local_id]);
169 }
170 }
171 }
172
173 public bool AllowedForcefulBans
174 {
175 get { return m_allowedForcefulBans; }
176 set { m_allowedForcefulBans = value; }
177 }
178
179 /// <summary>
180 /// Resets the sim to the default land object (full sim piece of land owned by the default user)
181 /// </summary>
182 public void ResetSimLandObjects()
183 {
184 //Remove all the land objects in the sim and add a blank, full sim land object set to public
185 lock (m_landList)
186 {
187 m_landList.Clear();
188 m_lastLandLocalID = LandChannel.START_LAND_LOCAL_ID - 1;
189 m_landIDList.Initialize();
190 }
191
192 ILandObject fullSimParcel = new LandObject(UUID.Zero, false, m_scene);
193
194 fullSimParcel.setLandBitmap(fullSimParcel.getSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize));
195 if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero)
196 fullSimParcel.landData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner;
197 else
198 fullSimParcel.landData.OwnerID = m_scene.RegionInfo.MasterAvatarAssignedUUID;
199 fullSimParcel.landData.ClaimDate = Util.UnixTimeSinceEpoch();
200 AddLandObject(fullSimParcel);
201 }
202
203 public List<ILandObject> AllParcels()
204 {
205 lock (m_landList)
206 {
207 return new List<ILandObject>(m_landList.Values);
208 }
209 }
210
211 public List<ILandObject> ParcelsNearPoint(Vector3 position)
212 {
213 List<ILandObject> parcelsNear = new List<ILandObject>();
214 for (int x = -4; x <= 4; x += 4)
215 {
216 for (int y = -4; y <= 4; y += 4)
217 {
218 ILandObject check = GetLandObject(position.X + x, position.Y + y);
219 if (check != null)
220 {
221 if (!parcelsNear.Contains(check))
222 {
223 parcelsNear.Add(check);
224 }
225 }
226 }
227 }
228
229 return parcelsNear;
230 }
231
232 public void SendYouAreBannedNotice(ScenePresence avatar)
233 {
234 if (AllowedForcefulBans)
235 {
236 avatar.ControllingClient.SendAlertMessage(
237 "You are not allowed on this parcel because you are banned. Please go away.");
238
239 avatar.PhysicsActor.Position =
240 new PhysicsVector(avatar.lastKnownAllowedPosition.X, avatar.lastKnownAllowedPosition.Y,
241 avatar.lastKnownAllowedPosition.Z);
242 avatar.PhysicsActor.Velocity = new PhysicsVector(0, 0, 0);
243 }
244 else
245 {
246 avatar.ControllingClient.SendAlertMessage(
247 "You are not allowed on this parcel because you are banned; however, the grid administrator has disabled ban lines globally. Please obey the land owner's requests or you can be banned from the entire sim!");
248 }
249 }
250
251 public void handleAvatarChangingParcel(ScenePresence avatar, int localLandID, UUID regionID)
252 {
253 if (m_scene.RegionInfo.RegionID == regionID)
254 {
255 ILandObject parcelAvatarIsEntering;
256 lock (m_landList)
257 {
258 parcelAvatarIsEntering = m_landList[localLandID];
259 }
260
261 if (parcelAvatarIsEntering != null)
262 {
263 if (avatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT)
264 {
265 if (parcelAvatarIsEntering.isBannedFromLand(avatar.UUID))
266 {
267 SendYouAreBannedNotice(avatar);
268 }
269 else if (parcelAvatarIsEntering.isRestrictedFromLand(avatar.UUID))
270 {
271 avatar.ControllingClient.SendAlertMessage(
272 "You are not allowed on this parcel because the land owner has restricted access. For now, you can enter, but please respect the land owner's decisions (or he can ban you!).");
273 }
274 else
275 {
276 avatar.sentMessageAboutRestrictedParcelFlyingDown = true;
277 }
278 }
279 else
280 {
281 avatar.sentMessageAboutRestrictedParcelFlyingDown = true;
282 }
283 }
284 }
285 }
286
287 public void SendOutNearestBanLine(IClientAPI avatar)
288 {
289 List<ScenePresence> avatars = m_scene.GetAvatars();
290 foreach (ScenePresence presence in avatars)
291 {
292 if (presence.UUID == avatar.AgentId)
293 {
294 List<ILandObject> checkLandParcels = ParcelsNearPoint(presence.AbsolutePosition);
295 foreach (ILandObject checkBan in checkLandParcels)
296 {
297 if (checkBan.isBannedFromLand(avatar.AgentId))
298 {
299 checkBan.sendLandProperties((int)ParcelStatus.CollisionBanned, false, (int)ParcelResult.Single, avatar);
300 return; //Only send one
301 }
302 if (checkBan.isRestrictedFromLand(avatar.AgentId))
303 {
304 checkBan.sendLandProperties((int)ParcelStatus.CollisionNotOnAccessList, false, (int)ParcelResult.Single, avatar);
305 return; //Only send one
306 }
307 }
308 return;
309 }
310 }
311 }
312
313 public void SendLandUpdate(ScenePresence avatar, bool force)
314 {
315 ILandObject over = GetLandObject((int)Math.Min(255, Math.Max(0, Math.Round(avatar.AbsolutePosition.X))),
316 (int)Math.Min(255, Math.Max(0, Math.Round(avatar.AbsolutePosition.Y))));
317
318 if (over != null)
319 {
320 if (force)
321 {
322 if (!avatar.IsChildAgent)
323 {
324 over.sendLandUpdateToClient(avatar.ControllingClient);
325 m_scene.EventManager.TriggerAvatarEnteringNewParcel(avatar, over.landData.LocalID,
326 m_scene.RegionInfo.RegionID);
327 }
328 }
329
330 if (avatar.currentParcelUUID != over.landData.GlobalID)
331 {
332 if (!avatar.IsChildAgent)
333 {
334 over.sendLandUpdateToClient(avatar.ControllingClient);
335 avatar.currentParcelUUID = over.landData.GlobalID;
336 m_scene.EventManager.TriggerAvatarEnteringNewParcel(avatar, over.landData.LocalID,
337 m_scene.RegionInfo.RegionID);
338 }
339 }
340 }
341 }
342
343 public void SendLandUpdate(ScenePresence avatar)
344 {
345 SendLandUpdate(avatar, false);
346 }
347
348 public void handleSignificantClientMovement(IClientAPI remote_client)
349 {
350 ScenePresence clientAvatar = m_scene.GetScenePresence(remote_client.AgentId);
351
352 if (clientAvatar != null)
353 {
354 SendLandUpdate(clientAvatar);
355 SendOutNearestBanLine(remote_client);
356 ILandObject parcel = GetLandObject(clientAvatar.AbsolutePosition.X, clientAvatar.AbsolutePosition.Y);
357 if (parcel != null)
358 {
359 if (clientAvatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT &&
360 clientAvatar.sentMessageAboutRestrictedParcelFlyingDown)
361 {
362 handleAvatarChangingParcel(clientAvatar, parcel.landData.LocalID, m_scene.RegionInfo.RegionID);
363 //They are going below the safety line!
364 if (!parcel.isBannedFromLand(clientAvatar.UUID))
365 {
366 clientAvatar.sentMessageAboutRestrictedParcelFlyingDown = false;
367 }
368 }
369 else if (clientAvatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT &&
370 parcel.isBannedFromLand(clientAvatar.UUID))
371 {
372 SendYouAreBannedNotice(clientAvatar);
373 }
374 }
375 }
376 }
377
378 public void handleAnyClientMovement(ScenePresence avatar)
379 //Like handleSignificantClientMovement, but called with an AgentUpdate regardless of distance.
380 {
381 ILandObject over = GetLandObject(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y);
382 if (over != null)
383 {
384 if (!over.isBannedFromLand(avatar.UUID) || avatar.AbsolutePosition.Z >= LandChannel.BAN_LINE_SAFETY_HIEGHT)
385 {
386 avatar.lastKnownAllowedPosition =
387 new Vector3(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z);
388 }
389 }
390 }
391
392
393 public void handleParcelAccessRequest(UUID agentID, UUID sessionID, uint flags, int sequenceID,
394 int landLocalID, IClientAPI remote_client)
395 {
396 ILandObject land;
397 lock (m_landList)
398 {
399 m_landList.TryGetValue(landLocalID, out land);
400 }
401
402 if (land != null)
403 {
404 m_landList[landLocalID].sendAccessList(agentID, sessionID, flags, sequenceID, remote_client);
405 }
406 }
407
408 public void handleParcelAccessUpdateRequest(UUID agentID, UUID sessionID, uint flags, int landLocalID,
409 List<ParcelManager.ParcelAccessEntry> entries,
410 IClientAPI remote_client)
411 {
412 ILandObject land;
413 lock (m_landList)
414 {
415 m_landList.TryGetValue(landLocalID, out land);
416 }
417
418 if (land != null)
419 {
420 if (agentID == land.landData.OwnerID)
421 {
422 land.updateAccessList(flags, entries, remote_client);
423 }
424 }
425 else
426 {
427 m_log.WarnFormat("[LAND]: Invalid local land ID {0}", landLocalID);
428 }
429 }
430
431 /// <summary>
432 /// Creates a basic Parcel object without an owner (a zeroed key)
433 /// </summary>
434 /// <returns></returns>
435 public ILandObject CreateBaseLand()
436 {
437 return new LandObject(UUID.Zero, false, m_scene);
438 }
439
440 /// <summary>
441 /// Adds a land object to the stored list and adds them to the landIDList to what they own
442 /// </summary>
443 /// <param name="new_land">The land object being added</param>
444 public ILandObject AddLandObject(ILandObject land)
445 {
446 ILandObject new_land = land.Copy();
447
448 lock (m_landList)
449 {
450 int newLandLocalID = ++m_lastLandLocalID;
451 new_land.landData.LocalID = newLandLocalID;
452
453 bool[,] landBitmap = new_land.getLandBitmap();
454 for (int x = 0; x < 64; x++)
455 {
456 for (int y = 0; y < 64; y++)
457 {
458 if (landBitmap[x, y])
459 {
460 m_landIDList[x, y] = newLandLocalID;
461 }
462 }
463 }
464
465 m_landList.Add(newLandLocalID, new_land);
466 }
467
468 new_land.forceUpdateLandInfo();
469 m_scene.EventManager.TriggerLandObjectAdded(new_land);
470 return new_land;
471 }
472
473 /// <summary>
474 /// Removes a land object from the list. Will not remove if local_id is still owning an area in landIDList
475 /// </summary>
476 /// <param name="local_id">Land.localID of the peice of land to remove.</param>
477 public void removeLandObject(int local_id)
478 {
479 lock (m_landList)
480 {
481 for (int x = 0; x < 64; x++)
482 {
483 for (int y = 0; y < 64; y++)
484 {
485 if (m_landIDList[x, y] == local_id)
486 {
487 m_log.WarnFormat("[LAND]: Not removing land object {0}; still being used at {1}, {2}",
488 local_id, x, y);
489 return;
490 //throw new Exception("Could not remove land object. Still being used at " + x + ", " + y);
491 }
492 }
493 }
494
495 m_scene.EventManager.TriggerLandObjectRemoved(m_landList[local_id].landData.GlobalID);
496 m_landList.Remove(local_id);
497 }
498 }
499
500 private void performFinalLandJoin(ILandObject master, ILandObject slave)
501 {
502 bool[,] landBitmapSlave = slave.getLandBitmap();
503 lock (m_landList)
504 {
505 for (int x = 0; x < 64; x++)
506 {
507 for (int y = 0; y < 64; y++)
508 {
509 if (landBitmapSlave[x, y])
510 {
511 m_landIDList[x, y] = master.landData.LocalID;
512 }
513 }
514 }
515 }
516
517 removeLandObject(slave.landData.LocalID);
518 UpdateLandObject(master.landData.LocalID, master.landData);
519 }
520
521 public ILandObject GetLandObject(int parcelLocalID)
522 {
523 lock (m_landList)
524 {
525 if (m_landList.ContainsKey(parcelLocalID))
526 {
527 return m_landList[parcelLocalID];
528 }
529 }
530 return null;
531 }
532
533 /// <summary>
534 /// Get the land object at the specified point
535 /// </summary>
536 /// <param name="x_float">Value between 0 - 256 on the x axis of the point</param>
537 /// <param name="y_float">Value between 0 - 256 on the y axis of the point</param>
538 /// <returns>Land object at the point supplied</returns>
539 public ILandObject GetLandObject(float x_float, float y_float)
540 {
541 int x;
542 int y;
543
544 try
545 {
546 x = Convert.ToInt32(Math.Floor(Convert.ToDouble(x_float) / 4.0));
547 y = Convert.ToInt32(Math.Floor(Convert.ToDouble(y_float) / 4.0));
548 }
549 catch (OverflowException)
550 {
551 return null;
552 }
553
554 if (x >= 64 || y >= 64 || x < 0 || y < 0)
555 {
556 return null;
557 }
558 lock (m_landList)
559 {
560 // Corner case. If an autoreturn happens during sim startup
561 // we will come here with the list uninitialized
562 //
563 if (m_landList.ContainsKey(m_landIDList[x, y]))
564 return m_landList[m_landIDList[x, y]];
565 return null;
566 }
567 }
568
569 public ILandObject GetLandObject(int x, int y)
570 {
571 if (x >= Convert.ToInt32(Constants.RegionSize) || y >= Convert.ToInt32(Constants.RegionSize) || x < 0 || y < 0)
572 {
573 // These exceptions here will cause a lot of complaints from the users specifically because
574 // they happen every time at border crossings
575 throw new Exception("Error: Parcel not found at point " + x + ", " + y);
576 }
577 lock (m_landIDList)
578 {
579 return m_landList[m_landIDList[x / 4, y / 4]];
580 }
581 }
582
583 #endregion
584
585 #region Parcel Modification
586
587 public void ResetAllLandPrimCounts()
588 {
589 lock (m_landList)
590 {
591 foreach (LandObject p in m_landList.Values)
592 {
593 p.resetLandPrimCounts();
594 }
595 }
596 }
597
598 public void SetPrimsTainted()
599 {
600 m_landPrimCountTainted = true;
601 }
602
603 public bool IsLandPrimCountTainted()
604 {
605 return m_landPrimCountTainted;
606 }
607
608 public void AddPrimToLandPrimCounts(SceneObjectGroup obj)
609 {
610 Vector3 position = obj.AbsolutePosition;
611 ILandObject landUnderPrim = GetLandObject(position.X, position.Y);
612 if (landUnderPrim != null)
613 {
614 landUnderPrim.addPrimToCount(obj);
615 }
616 }
617
618 public void RemovePrimFromLandPrimCounts(SceneObjectGroup obj)
619 {
620
621 lock (m_landList)
622 {
623 foreach (LandObject p in m_landList.Values)
624 {
625 p.removePrimFromCount(obj);
626 }
627 }
628 }
629
630 public void FinalizeLandPrimCountUpdate()
631 {
632 //Get Simwide prim count for owner
633 Dictionary<UUID, List<LandObject>> landOwnersAndParcels = new Dictionary<UUID, List<LandObject>>();
634 lock (m_landList)
635 {
636 foreach (LandObject p in m_landList.Values)
637 {
638 if (!landOwnersAndParcels.ContainsKey(p.landData.OwnerID))
639 {
640 List<LandObject> tempList = new List<LandObject>();
641 tempList.Add(p);
642 landOwnersAndParcels.Add(p.landData.OwnerID, tempList);
643 }
644 else
645 {
646 landOwnersAndParcels[p.landData.OwnerID].Add(p);
647 }
648 }
649 }
650
651 foreach (UUID owner in landOwnersAndParcels.Keys)
652 {
653 int simArea = 0;
654 int simPrims = 0;
655 foreach (LandObject p in landOwnersAndParcels[owner])
656 {
657 simArea += p.landData.Area;
658 simPrims += p.landData.OwnerPrims + p.landData.OtherPrims + p.landData.GroupPrims +
659 p.landData.SelectedPrims;
660 }
661
662 foreach (LandObject p in landOwnersAndParcels[owner])
663 {
664 p.landData.SimwideArea = simArea;
665 p.landData.SimwidePrims = simPrims;
666 }
667 }
668 }
669
670 public void UpdateLandPrimCounts()
671 {
672 ResetAllLandPrimCounts();
673 foreach (EntityBase obj in m_scene.Entities)
674 {
675 if (obj != null)
676 {
677 if ((obj is SceneObjectGroup) && !obj.IsDeleted && !((SceneObjectGroup) obj).IsAttachment)
678 {
679 m_scene.EventManager.TriggerParcelPrimCountAdd((SceneObjectGroup) obj);
680 }
681 }
682 }
683 FinalizeLandPrimCountUpdate();
684 m_landPrimCountTainted = false;
685 }
686
687 public void PerformParcelPrimCountUpdate()
688 {
689 ResetAllLandPrimCounts();
690 m_scene.EventManager.TriggerParcelPrimCountUpdate();
691 FinalizeLandPrimCountUpdate();
692 m_landPrimCountTainted = false;
693 }
694
695 /// <summary>
696 /// Subdivides a piece of land
697 /// </summary>
698 /// <param name="start_x">West Point</param>
699 /// <param name="start_y">South Point</param>
700 /// <param name="end_x">East Point</param>
701 /// <param name="end_y">North Point</param>
702 /// <param name="attempting_user_id">UUID of user who is trying to subdivide</param>
703 /// <returns>Returns true if successful</returns>
704 private void subdivide(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id)
705 {
706 //First, lets loop through the points and make sure they are all in the same peice of land
707 //Get the land object at start
708
709 ILandObject startLandObject = GetLandObject(start_x, start_y);
710
711 if (startLandObject == null) return;
712
713 //Loop through the points
714 try
715 {
716 int totalX = end_x - start_x;
717 int totalY = end_y - start_y;
718 for (int y = 0; y < totalY; y++)
719 {
720 for (int x = 0; x < totalX; x++)
721 {
722 ILandObject tempLandObject = GetLandObject(start_x + x, start_y + y);
723 if (tempLandObject == null) return;
724 if (tempLandObject != startLandObject) return;
725 }
726 }
727 }
728 catch (Exception)
729 {
730 return;
731 }
732
733 //If we are still here, then they are subdividing within one piece of land
734 //Check owner
735 if (!m_scene.Permissions.CanEditParcel(attempting_user_id, startLandObject))
736 {
737 return;
738 }
739
740 //Lets create a new land object with bitmap activated at that point (keeping the old land objects info)
741 ILandObject newLand = startLandObject.Copy();
742 newLand.landData.Name = "Subdivision of " + newLand.landData.Name;
743 newLand.landData.GlobalID = UUID.Random();
744
745 newLand.setLandBitmap(newLand.getSquareLandBitmap(start_x, start_y, end_x, end_y));
746
747 //Now, lets set the subdivision area of the original to false
748 int startLandObjectIndex = startLandObject.landData.LocalID;
749 lock (m_landList)
750 {
751 m_landList[startLandObjectIndex].setLandBitmap(
752 newLand.modifyLandBitmapSquare(startLandObject.getLandBitmap(), start_x, start_y, end_x, end_y, false));
753 m_landList[startLandObjectIndex].forceUpdateLandInfo();
754 }
755
756 SetPrimsTainted();
757
758 //Now add the new land object
759 ILandObject result = AddLandObject(newLand);
760 UpdateLandObject(startLandObject.landData.LocalID, startLandObject.landData);
761 result.sendLandUpdateToAvatarsOverMe();
762 }
763
764 /// <summary>
765 /// Join 2 land objects together
766 /// </summary>
767 /// <param name="start_x">x value in first piece of land</param>
768 /// <param name="start_y">y value in first piece of land</param>
769 /// <param name="end_x">x value in second peice of land</param>
770 /// <param name="end_y">y value in second peice of land</param>
771 /// <param name="attempting_user_id">UUID of the avatar trying to join the land objects</param>
772 /// <returns>Returns true if successful</returns>
773 private void join(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id)
774 {
775 end_x -= 4;
776 end_y -= 4;
777
778 List<ILandObject> selectedLandObjects = new List<ILandObject>();
779 int stepYSelected;
780 for (stepYSelected = start_y; stepYSelected <= end_y; stepYSelected += 4)
781 {
782 int stepXSelected;
783 for (stepXSelected = start_x; stepXSelected <= end_x; stepXSelected += 4)
784 {
785 ILandObject p = GetLandObject(stepXSelected, stepYSelected);
786
787 if (p != null)
788 {
789 if (!selectedLandObjects.Contains(p))
790 {
791 selectedLandObjects.Add(p);
792 }
793 }
794 }
795 }
796 ILandObject masterLandObject = selectedLandObjects[0];
797 selectedLandObjects.RemoveAt(0);
798
799 if (selectedLandObjects.Count < 1)
800 {
801 return;
802 }
803 if (!m_scene.Permissions.CanEditParcel(attempting_user_id, masterLandObject))
804 {
805 return;
806 }
807 foreach (ILandObject p in selectedLandObjects)
808 {
809 if (p.landData.OwnerID != masterLandObject.landData.OwnerID)
810 {
811 return;
812 }
813 }
814
815 lock (m_landList)
816 {
817 foreach (ILandObject slaveLandObject in selectedLandObjects)
818 {
819 m_landList[masterLandObject.landData.LocalID].setLandBitmap(
820 slaveLandObject.mergeLandBitmaps(masterLandObject.getLandBitmap(), slaveLandObject.getLandBitmap()));
821 performFinalLandJoin(masterLandObject, slaveLandObject);
822 }
823 }
824 SetPrimsTainted();
825
826 masterLandObject.sendLandUpdateToAvatarsOverMe();
827 }
828
829 #endregion
830
831 #region Parcel Updating
832
833 /// <summary>
834 /// Where we send the ParcelOverlay packet to the client
835 /// </summary>
836 /// <param name="remote_client">The object representing the client</param>
837 public void SendParcelOverlay(IClientAPI remote_client)
838 {
839 const int LAND_BLOCKS_PER_PACKET = 1024;
840
841 byte[] byteArray = new byte[LAND_BLOCKS_PER_PACKET];
842 int byteArrayCount = 0;
843 int sequenceID = 0;
844
845 for (int y = 0; y < 64; y++)
846 {
847 for (int x = 0; x < 64; x++)
848 {
849 byte tempByte = 0; //This represents the byte for the current 4x4
850
851 ILandObject currentParcelBlock = GetLandObject(x * 4, y * 4);
852
853 if (currentParcelBlock != null)
854 {
855 if (currentParcelBlock.landData.OwnerID == remote_client.AgentId)
856 {
857 //Owner Flag
858 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_REQUESTER);
859 }
860 else if (currentParcelBlock.landData.SalePrice > 0 &&
861 (currentParcelBlock.landData.AuthBuyerID == UUID.Zero ||
862 currentParcelBlock.landData.AuthBuyerID == remote_client.AgentId))
863 {
864 //Sale Flag
865 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_IS_FOR_SALE);
866 }
867 else if (currentParcelBlock.landData.OwnerID == UUID.Zero)
868 {
869 //Public Flag
870 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_PUBLIC);
871 }
872 else
873 {
874 //Other Flag
875 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_OTHER);
876 }
877
878 //Now for border control
879
880 ILandObject westParcel = null;
881 ILandObject southParcel = null;
882 if (x > 0)
883 {
884 westParcel = GetLandObject((x - 1) * 4, y * 4);
885 }
886 if (y > 0)
887 {
888 southParcel = GetLandObject(x * 4, (y - 1) * 4);
889 }
890
891 if (x == 0)
892 {
893 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST);
894 }
895 else if (westParcel != null && westParcel != currentParcelBlock)
896 {
897 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST);
898 }
899
900 if (y == 0)
901 {
902 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH);
903 }
904 else if (southParcel != null && southParcel != currentParcelBlock)
905 {
906 tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH);
907 }
908
909 byteArray[byteArrayCount] = tempByte;
910 byteArrayCount++;
911 if (byteArrayCount >= LAND_BLOCKS_PER_PACKET)
912 {
913 remote_client.SendLandParcelOverlay(byteArray, sequenceID);
914 byteArrayCount = 0;
915 sequenceID++;
916 byteArray = new byte[LAND_BLOCKS_PER_PACKET];
917 }
918 }
919 }
920 }
921 }
922
923 public void handleParcelPropertiesRequest(int start_x, int start_y, int end_x, int end_y, int sequence_id,
924 bool snap_selection, IClientAPI remote_client)
925 {
926 //Get the land objects within the bounds
927 List<ILandObject> temp = new List<ILandObject>();
928 int inc_x = end_x - start_x;
929 int inc_y = end_y - start_y;
930 for (int x = 0; x < inc_x; x++)
931 {
932 for (int y = 0; y < inc_y; y++)
933 {
934 ILandObject currentParcel = GetLandObject(start_x + x, start_y + y);
935
936 if (currentParcel != null)
937 {
938 if (!temp.Contains(currentParcel))
939 {
940 currentParcel.forceUpdateLandInfo();
941 temp.Add(currentParcel);
942 }
943 }
944 }
945 }
946
947 int requestResult = LandChannel.LAND_RESULT_SINGLE;
948 if (temp.Count > 1)
949 {
950 requestResult = LandChannel.LAND_RESULT_MULTIPLE;
951 }
952
953 for (int i = 0; i < temp.Count; i++)
954 {
955 temp[i].sendLandProperties(sequence_id, snap_selection, requestResult, remote_client);
956 }
957
958 SendParcelOverlay(remote_client);
959 }
960
961 public void handleParcelPropertiesUpdateRequest(LandUpdateArgs args, int localID, IClientAPI remote_client)
962 {
963 ILandObject land;
964 lock (m_landList)
965 {
966 m_landList.TryGetValue(localID, out land);
967 }
968
969 if (land != null) land.updateLandProperties(args, remote_client);
970 }
971
972 public void handleParcelDivideRequest(int west, int south, int east, int north, IClientAPI remote_client)
973 {
974 subdivide(west, south, east, north, remote_client.AgentId);
975 }
976
977 public void handleParcelJoinRequest(int west, int south, int east, int north, IClientAPI remote_client)
978 {
979 join(west, south, east, north, remote_client.AgentId);
980 }
981
982 public void handleParcelSelectObjectsRequest(int local_id, int request_type, List<UUID> returnIDs, IClientAPI remote_client)
983 {
984 m_landList[local_id].sendForceObjectSelect(local_id, request_type, returnIDs, remote_client);
985 }
986
987 public void handleParcelObjectOwnersRequest(int local_id, IClientAPI remote_client)
988 {
989 ILandObject land;
990 lock (m_landList)
991 {
992 m_landList.TryGetValue(local_id, out land);
993 }
994
995 if (land != null)
996 {
997 m_landList[local_id].sendLandObjectOwners(remote_client);
998 }
999 else
1000 {
1001 m_log.WarnFormat("[PARCEL]: Invalid land object {0} passed for parcel object owner request", local_id);
1002 }
1003 }
1004
1005 public void handleParcelGodForceOwner(int local_id, UUID ownerID, IClientAPI remote_client)
1006 {
1007 ILandObject land;
1008 lock (m_landList)
1009 {
1010 m_landList.TryGetValue(local_id, out land);
1011 }
1012
1013 if (land != null)
1014 {
1015 if (m_scene.Permissions.IsGod(remote_client.AgentId))
1016 {
1017 land.landData.OwnerID = ownerID;
1018
1019 m_scene.Broadcast(SendParcelOverlay);
1020 land.sendLandUpdateToClient(remote_client);
1021 }
1022 }
1023 }
1024
1025 public void handleParcelAbandonRequest(int local_id, IClientAPI remote_client)
1026 {
1027 ILandObject land;
1028 lock (m_landList)
1029 {
1030 m_landList.TryGetValue(local_id, out land);
1031 }
1032
1033 if (land != null)
1034 {
1035 if (m_scene.Permissions.CanAbandonParcel(remote_client.AgentId, land))
1036 {
1037 if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero)
1038 land.landData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner;
1039 else
1040 land.landData.OwnerID = m_scene.RegionInfo.MasterAvatarAssignedUUID;
1041 m_scene.Broadcast(SendParcelOverlay);
1042 land.sendLandUpdateToClient(remote_client);
1043 }
1044 }
1045 }
1046
1047 public void handleParcelReclaim(int local_id, IClientAPI remote_client)
1048 {
1049 ILandObject land;
1050 lock (m_landList)
1051 {
1052 m_landList.TryGetValue(local_id, out land);
1053 }
1054
1055 if (land != null)
1056 {
1057 if (m_scene.Permissions.CanReclaimParcel(remote_client.AgentId, land))
1058 {
1059 if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero)
1060 land.landData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner;
1061 else
1062 land.landData.OwnerID = m_scene.RegionInfo.MasterAvatarAssignedUUID;
1063 land.landData.ClaimDate = Util.UnixTimeSinceEpoch();
1064 m_scene.Broadcast(SendParcelOverlay);
1065 land.sendLandUpdateToClient(remote_client);
1066 }
1067 }
1068 }
1069 #endregion
1070
1071 // If the economy has been validated by the economy module,
1072 // and land has been validated as well, this method transfers
1073 // the land ownership
1074
1075 public void handleLandBuyRequest(Object o, EventManager.LandBuyArgs e)
1076 {
1077 if (e.economyValidated && e.landValidated)
1078 {
1079 ILandObject land;
1080 lock (m_landList)
1081 {
1082 m_landList.TryGetValue(e.parcelLocalID, out land);
1083 }
1084
1085 if (land != null)
1086 {
1087 land.updateLandSold(e.agentId, e.groupId, e.groupOwned, (uint)e.transactionID, e.parcelPrice, e.parcelArea);
1088 }
1089 }
1090 }
1091
1092 // After receiving a land buy packet, first the data needs to
1093 // be validated. This method validates the right to buy the
1094 // parcel
1095
1096 public void handleLandValidationRequest(Object o, EventManager.LandBuyArgs e)
1097 {
1098 if (e.landValidated == false)
1099 {
1100 ILandObject lob = null;
1101 lock (m_landList)
1102 {
1103 m_landList.TryGetValue(e.parcelLocalID, out lob);
1104 }
1105
1106 if (lob != null)
1107 {
1108 UUID AuthorizedID = lob.landData.AuthBuyerID;
1109 int saleprice = lob.landData.SalePrice;
1110 UUID pOwnerID = lob.landData.OwnerID;
1111
1112 bool landforsale = ((lob.landData.Flags &
1113 (uint)(Parcel.ParcelFlags.ForSale | Parcel.ParcelFlags.ForSaleObjects | Parcel.ParcelFlags.SellParcelObjects)) != 0);
1114 if ((AuthorizedID == UUID.Zero || AuthorizedID == e.agentId) && e.parcelPrice >= saleprice && landforsale)
1115 {
1116 // TODO I don't think we have to lock it here, no?
1117 //lock (e)
1118 //{
1119 e.parcelOwnerID = pOwnerID;
1120 e.landValidated = true;
1121 //}
1122 }
1123 }
1124 }
1125 }
1126
1127 #region Land Object From Storage Functions
1128
1129 public void IncomingLandObjectsFromStorage(List<LandData> data)
1130 {
1131 for (int i = 0; i < data.Count; i++)
1132 {
1133 IncomingLandObjectFromStorage(data[i]);
1134 }
1135 }
1136
1137 public void IncomingLandObjectFromStorage(LandData data)
1138 {
1139 ILandObject new_land = new LandObject(data.OwnerID, data.IsGroupOwned, m_scene);
1140 new_land.landData = data.Copy();
1141 new_land.setLandBitmapFromByteArray();
1142 AddLandObject(new_land);
1143 }
1144
1145 public void ReturnObjectsInParcel(int localID, uint returnType, UUID[] agentIDs, UUID[] taskIDs, IClientAPI remoteClient)
1146 {
1147 ILandObject selectedParcel = null;
1148 lock (m_landList)
1149 {
1150 m_landList.TryGetValue(localID, out selectedParcel);
1151 }
1152
1153 if (selectedParcel == null) return;
1154
1155 selectedParcel.returnLandObjects(returnType, agentIDs, taskIDs, remoteClient);
1156 }
1157
1158 public void NoLandDataFromStorage()
1159 {
1160 ResetSimLandObjects();
1161 }
1162
1163 #endregion
1164
1165 public void setParcelObjectMaxOverride(overrideParcelMaxPrimCountDelegate overrideDel)
1166 {
1167 lock (m_landList)
1168 {
1169 foreach (LandObject obj in m_landList.Values)
1170 {
1171 obj.setParcelObjectMaxOverride(overrideDel);
1172 }
1173 }
1174 }
1175
1176 public void setSimulatorObjectMaxOverride(overrideSimulatorMaxPrimCountDelegate overrideDel)
1177 {
1178 }
1179
1180 #region CAPS handler
1181
1182 private void OnRegisterCaps(UUID agentID, Caps caps)
1183 {
1184 string capsBase = "/CAPS/" + caps.CapsObjectPath;
1185 caps.RegisterHandler("RemoteParcelRequest",
1186 new RestStreamHandler("POST", capsBase + remoteParcelRequestPath,
1187 delegate(string request, string path, string param,
1188 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
1189 {
1190 return RemoteParcelRequest(request, path, param, agentID, caps);
1191 }));
1192 }
1193
1194 // we cheat here: As we don't have (and want) a grid-global parcel-store, we can't return the
1195 // "real" parcelID, because we wouldn't be able to map that to the region the parcel belongs to.
1196 // So, we create a "fake" parcelID by using the regionHandle (64 bit), and the local (integer) x
1197 // and y coordinate (each 8 bit), encoded in a UUID (128 bit).
1198 //
1199 // Request format:
1200 // <llsd>
1201 // <map>
1202 // <key>location</key>
1203 // <array>
1204 // <real>1.23</real>
1205 // <real>45..6</real>
1206 // <real>78.9</real>
1207 // </array>
1208 // <key>region_id</key>
1209 // <uuid>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</uuid>
1210 // </map>
1211 // </llsd>
1212 private string RemoteParcelRequest(string request, string path, string param, UUID agentID, Caps caps)
1213 {
1214 UUID parcelID = UUID.Zero;
1215 try
1216 {
1217 Hashtable hash = new Hashtable();
1218 hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request));
1219 if (hash.ContainsKey("region_id") && hash.ContainsKey("location"))
1220 {
1221 UUID regionID = (UUID)hash["region_id"];
1222 ArrayList list = (ArrayList)hash["location"];
1223 uint x = (uint)(double)list[0];
1224 uint y = (uint)(double)list[1];
1225 if (hash.ContainsKey("region_handle"))
1226 {
1227 // if you do a "About Landmark" on a landmark a second time, the viewer sends the
1228 // region_handle it got earlier via RegionHandleRequest
1229 ulong regionHandle = Util.BytesToUInt64Big((byte[])hash["region_handle"]);
1230 parcelID = Util.BuildFakeParcelID(regionHandle, x, y);
1231 }
1232 else if (regionID == m_scene.RegionInfo.RegionID)
1233 {
1234 // a parcel request for a local parcel => no need to query the grid
1235 parcelID = Util.BuildFakeParcelID(m_scene.RegionInfo.RegionHandle, x, y);
1236 }
1237 else
1238 {
1239 // a parcel request for a parcel in another region. Ask the grid about the region
1240 RegionInfo info = m_scene.CommsManager.GridService.RequestNeighbourInfo(regionID);
1241 if (info != null)
1242 parcelID = Util.BuildFakeParcelID(info.RegionHandle, x, y);
1243 }
1244 }
1245 }
1246 catch (LLSD.LLSDParseException e)
1247 {
1248 m_log.ErrorFormat("[LAND] Fetch error: {0}", e.Message);
1249 m_log.ErrorFormat("[LAND] ... in request {0}", request);
1250 }
1251 catch(InvalidCastException)
1252 {
1253 m_log.ErrorFormat("[LAND] Wrong type in request {0}", request);
1254 }
1255
1256 LLSDRemoteParcelResponse response = new LLSDRemoteParcelResponse();
1257 response.parcel_id = parcelID;
1258 m_log.DebugFormat("[LAND] got parcelID {0}", parcelID);
1259
1260 return LLSDHelpers.SerialiseLLSDReply(response);
1261 }
1262
1263 #endregion
1264
1265 private void handleParcelDwell(int localID, IClientAPI remoteClient)
1266 {
1267 ILandObject selectedParcel = null;
1268 lock (m_landList)
1269 {
1270 if (!m_landList.TryGetValue(localID, out selectedParcel))
1271 return;
1272 }
1273
1274 remoteClient.SendParcelDwellReply(localID, selectedParcel.landData.GlobalID, selectedParcel.landData.Dwell);
1275 }
1276
1277 private void handleParcelInfo(IClientAPI remoteClient, UUID parcelID)
1278 {
1279 if (parcelID == UUID.Zero)
1280 return;
1281
1282 ExtendedLandData data = (ExtendedLandData)parcelInfoCache.Get(parcelID, delegate(UUID parcel) {
1283 // assume we've got the parcelID we just computed in RemoteParcelRequest
1284 ExtendedLandData extLandData = new ExtendedLandData();
1285 Util.ParseFakeParcelID(parcel, out extLandData.regionHandle, out extLandData.x, out extLandData.y);
1286 m_log.DebugFormat("[LAND] got parcelinfo request for regionHandle {0}, x/y {1}/{2}",
1287 extLandData.regionHandle, extLandData.x, extLandData.y);
1288
1289 // for this region or for somewhere else?
1290 if (extLandData.regionHandle == m_scene.RegionInfo.RegionHandle)
1291 {
1292 extLandData.landData = this.GetLandObject(extLandData.x, extLandData.y).landData;
1293 }
1294 else
1295 {
1296 extLandData.landData = m_scene.CommsManager.GridService.RequestLandData(extLandData.regionHandle,
1297 extLandData.x,
1298 extLandData.y);
1299 if (extLandData.landData == null)
1300 {
1301 // we didn't find the region/land => don't cache
1302 return null;
1303 }
1304 }
1305 return extLandData;
1306 });
1307
1308 if (data != null) // if we found some data, send it
1309 {
1310 RegionInfo info;
1311 if (data.regionHandle == m_scene.RegionInfo.RegionHandle)
1312 {
1313 info = m_scene.RegionInfo;
1314 }
1315 else
1316 {
1317 // most likely still cached from building the extLandData entry
1318 info = m_scene.CommsManager.GridService.RequestNeighbourInfo(data.regionHandle);
1319 }
1320 // we need to transfer the fake parcelID, not the one in landData, so the viewer can match it to the landmark.
1321 m_log.DebugFormat("[LAND] got parcelinfo for parcel {0} in region {1}; sending...",
1322 data.landData.Name, data.regionHandle);
1323 remoteClient.SendParcelInfo(info, data.landData, parcelID, data.x, data.y);
1324 }
1325 else
1326 m_log.Debug("[LAND] got no parcelinfo; not sending");
1327 }
1328
1329 public void setParcelOtherCleanTime(IClientAPI remoteClient, int localID, int otherCleanTime)
1330 {
1331 ILandObject land;
1332 lock (m_landList)
1333 {
1334 m_landList.TryGetValue(localID, out land);
1335 }
1336
1337 if (land == null) return;
1338
1339 if (!m_scene.Permissions.CanEditParcel(remoteClient.AgentId, land))
1340 return;
1341
1342 land.landData.OtherCleanTime = otherCleanTime;
1343
1344 UpdateLandObject(localID, land.landData);
1345 }
1346 }
1347}