/*
* Copyright (c) 2006-2008, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using System.Reflection;
using OpenMetaverse.Packets;
using OpenMetaverse.StructuredData;
namespace OpenMetaverse
{
#region Structs
///
/// Some information about a parcel of land returned from a DirectoryManager search
///
public struct ParcelInfo
{
/// Global Key of record
public UUID ID;
/// Parcel Owners
public UUID OwnerID;
/// Name field of parcel, limited to 128 characters
public string Name;
/// Description field of parcel, limited to 256 characters
public string Description;
/// Total Square meters of parcel
public int ActualArea;
/// Total area billable as Tier, for group owned land this will be 10% less than ActualArea
public int BillableArea;
/// True of parcel is in Mature simulator
public bool Mature;
/// Grid global X position of parcel
public float GlobalX;
/// Grid global Y position of parcel
public float GlobalY;
/// Grid global Z position of parcel (not used)
public float GlobalZ;
/// Name of simulator parcel is located in
public string SimName;
/// Texture of parcels display picture
public UUID SnapshotID;
/// Float representing calculated traffic based on time spent on parcel by avatars
public float Dwell;
/// Sale price of parcel (not used)
public int SalePrice;
/// Auction ID of parcel
public int AuctionID;
}
///
/// Parcel Media Information
///
public struct ParcelMedia
{
/// A byte, if 0x1 viewer should auto scale media to fit object
public byte MediaAutoScale;
/// A boolean, if true the viewer should loop the media
public bool MediaLoop;
/// The Asset UUID of the Texture which when applied to a
/// primitive will display the media
public UUID MediaID;
/// A URL which points to any Quicktime supported media type
public string MediaURL;
/// A description of the media
public string MediaDesc;
/// An Integer which represents the height of the media
public int MediaHeight;
/// An integer which represents the width of the media
public int MediaWidth;
/// A string which contains the mime type of the media
public string MediaType;
}
#endregion Structs
#region Parcel Class
///
/// Parcel of land, a portion of virtual real estate in a simulator
///
public struct Parcel
{
#region Enums
///
/// Various parcel properties
///
[Flags]
public enum ParcelFlags : uint
{
/// No flags set
None = 0,
/// Allow avatars to fly (a client-side only restriction)
AllowFly = 1 << 0,
/// Allow foreign scripts to run
AllowOtherScripts = 1 << 1,
/// This parcel is for sale
ForSale = 1 << 2,
/// Allow avatars to create a landmark on this parcel
AllowLandmark = 1 << 3,
/// Allows all avatars to edit the terrain on this parcel
AllowTerraform = 1 << 4,
/// Avatars have health and can take damage on this parcel.
/// If set, avatars can be killed and sent home here
AllowDamage = 1 << 5,
/// Foreign avatars can create objects here
CreateObjects = 1 << 6,
/// All objects on this parcel can be purchased
ForSaleObjects = 1 << 7,
/// Access is restricted to a group
UseAccessGroup = 1 << 8,
/// Access is restricted to a whitelist
UseAccessList = 1 << 9,
/// Ban blacklist is enabled
UseBanList = 1 << 10,
/// Unknown
UsePassList = 1 << 11,
/// List this parcel in the search directory
ShowDirectory = 1 << 12,
/// Allow personally owned parcels to be deeded to group
AllowDeedToGroup = 1 << 13,
/// If Deeded, owner contributes required tier to group parcel is deeded to
ContributeWithDeed = 1 << 14,
/// Restrict sounds originating on this parcel to the
/// parcel boundaries
SoundLocal = 1 << 15,
/// Objects on this parcel are sold when the land is
/// purchsaed
SellParcelObjects = 1 << 16,
/// Allow this parcel to be published on the web
AllowPublish = 1 << 17,
/// The information for this parcel is mature content
MaturePublish = 1 << 18,
/// The media URL is an HTML page
UrlWebPage = 1 << 19,
/// The media URL is a raw HTML string
UrlRawHtml = 1 << 20,
/// Restrict foreign object pushes
RestrictPushObject = 1 << 21,
/// Ban all non identified/transacted avatars
DenyAnonymous = 1 << 22,
// Ban all identified avatars [OBSOLETE]
//[Obsolete]
//DenyIdentified = 1 << 23,
// Ban all transacted avatars [OBSOLETE]
//[Obsolete]
//DenyTransacted = 1 << 24,
/// Allow group-owned scripts to run
AllowGroupScripts = 1 << 25,
/// Allow object creation by group members or group
/// objects
CreateGroupObjects = 1 << 26,
/// Allow all objects to enter this parcel
AllowAllObjectEntry = 1 << 27,
/// Only allow group and owner objects to enter this parcel
AllowGroupObjectEntry = 1 << 28,
/// Voice Enabled on this parcel
AllowVoiceChat = 1 << 29,
/// Use Estate Voice channel for Voice on this parcel
UseEstateVoiceChan = 1 << 30,
/// Deny Age Unverified Users
DenyAgeUnverified = 1U << 31
}
///
/// Parcel ownership status
///
public enum ParcelStatus : sbyte
{
/// Placeholder
None = -1,
/// Parcel is leased (owned) by an avatar or group
Leased = 0,
/// Parcel is in process of being leased (purchased) by an avatar or group
LeasePending = 1,
/// Parcel has been abandoned back to Governor Linden
Abandoned = 2
}
///
/// Category parcel is listed in under search
///
public enum ParcelCategory : sbyte
{
/// No assigned category
None = 0,
/// Linden Infohub or public area
Linden,
/// Adult themed area
Adult,
/// Arts and Culture
Arts,
/// Business
Business,
/// Educational
Educational,
/// Gaming
Gaming,
/// Hangout or Club
Hangout,
/// Newcomer friendly
Newcomer,
/// Parks and Nature
Park,
/// Residential
Residential,
/// Shopping
Shopping,
/// Not Used?
Stage,
/// Other
Other,
/// Not an actual category, only used for queries
Any = -1
}
#endregion Enums
///
public int RequestResult;
///
public int SequenceID;
/// Used by the viewer in conjunction with the BitMap
/// for highlighting the borders of a parcel
public bool SnapSelection;
///
public int SelfCount;
///
public int OtherCount;
///
public int PublicCount;
/// Simulator-local ID of this parcel
public int LocalID;
/// UUID of the owner of this parcel
public UUID OwnerID;
/// Whether the land is deeded to a group or not
public bool IsGroupOwned;
///
public uint AuctionID;
/// Date land was claimed
public DateTime ClaimDate;
/// Appears to always be zero
public int ClaimPrice;
/// This field is no longer used
public int RentPrice;
/// Minimum corner of the axis-aligned bounding box for this
/// parcel
public Vector3 AABBMin;
/// Maximum corner of the axis-aligned bounding box for this
/// parcel
public Vector3 AABBMax;
/// Bitmap describing land layout in 4x4m squares across the
/// entire region
public byte[] Bitmap;
/// Total parcel land area
public int Area;
///
public ParcelStatus Status;
/// Maximum primitives across the entire simulator
public int SimWideMaxPrims;
/// Total primitives across the entire simulator
public int SimWideTotalPrims;
/// Maximum number of primitives this parcel supports
public int MaxPrims;
/// Total number of primitives on this parcel
public int TotalPrims;
/// Total number of primitives owned by the parcel owner on
/// this parcel
public int OwnerPrims;
/// Total number of primitives owned by the parcel group on
/// this parcel
public int GroupPrims;
/// Total number of other primitives on this parcel
public int OtherPrims;
/// Total number of primitives you are currently selecting and
/// sitting on
public int SelectedPrims;
///
public float ParcelPrimBonus;
/// Autoreturn value in minutes for others' objects
public int OtherCleanTime;
///
public ParcelFlags Flags;
/// Sale price of the parcel, only useful if ForSale is set
/// The SalePrice will remain the same after an ownership
/// transfer (sale), so it can be used to see the purchase price after
/// a sale if the new owner has not changed it
public int SalePrice;
/// Parcel Name
public string Name;
/// Parcel Description
public string Desc;
/// URL For Music Stream
public string MusicURL;
///
public UUID GroupID;
/// Price for a temporary pass
public int PassPrice;
/// How long is pass valid for
public float PassHours;
///
public ParcelCategory Category;
/// Key of authorized buyer
public UUID AuthBuyerID;
/// Key of parcel snapshot
public UUID SnapshotID;
///
public Vector3 UserLocation;
///
public Vector3 UserLookAt;
///
public byte LandingType;
///
public float Dwell;
///
public bool RegionDenyAnonymous;
///
public bool RegionPushOverride;
/// Simulator Object, containing details from Simulator class
public Simulator Simulator;
/// Access list of who is whitelisted or blacklisted on this
/// parcel
public List AccessList;
/// TRUE of region denies access to age unverified users
public bool RegionDenyAgeUnverified;
/// true to obscure (hide) media url
public bool ObscureMedia;
/// true to obscure (hide) music url
public bool ObscureMusic;
/// A struct containing media details
public ParcelMedia Media;
///
/// Displays a parcel object in string format
///
/// string containing key=value pairs of a parcel object
public override string ToString()
{
string result = "";
Type parcelType = this.GetType();
FieldInfo[] fields = parcelType.GetFields();
foreach (FieldInfo field in fields)
{
result += (field.Name + " = " + field.GetValue(this) + " ");
}
return result;
}
///
/// Defalt constructor
///
/// Simulator this parcel resides in
/// Local ID of this parcel
public Parcel(Simulator simulator, int localID)
{
Simulator = simulator;
LocalID = localID;
RequestResult = 0;
SequenceID = 0;
SnapSelection = false;
SelfCount = 0;
OtherCount = 0;
PublicCount = 0;
OwnerID = UUID.Zero;
IsGroupOwned = false;
AuctionID = 0;
ClaimDate = Utils.Epoch;
ClaimPrice = 0;
RentPrice = 0;
AABBMin = Vector3.Zero;
AABBMax = Vector3.Zero;
Bitmap = new byte[0];
Area = 0;
Status = ParcelStatus.None;
SimWideMaxPrims = 0;
SimWideTotalPrims = 0;
MaxPrims = 0;
TotalPrims = 0;
OwnerPrims = 0;
GroupPrims = 0;
OtherPrims = 0;
SelectedPrims = 0;
ParcelPrimBonus = 0;
OtherCleanTime = 0;
Flags = ParcelFlags.None;
SalePrice = 0;
Name = String.Empty;
Desc = String.Empty;
MusicURL = String.Empty;
GroupID = UUID.Zero;
PassPrice = 0;
PassHours = 0;
Category = ParcelCategory.None;
AuthBuyerID = UUID.Zero;
SnapshotID = UUID.Zero;
UserLocation = Vector3.Zero;
UserLookAt = Vector3.Zero;
LandingType = 0x0;
Dwell = 0;
RegionDenyAnonymous = false;
RegionPushOverride = false;
AccessList = new List(0);
RegionDenyAgeUnverified = false;
Media = new ParcelMedia();
ObscureMedia = false;
ObscureMusic = false;
}
///
/// Update the simulator with any local changes to this Parcel object
///
/// Whether we want the simulator to confirm
/// the update with a reply packet or not
public void Update(bool wantReply)
{
ParcelPropertiesUpdatePacket request = new ParcelPropertiesUpdatePacket();
request.AgentData.AgentID = Simulator.Client.Self.AgentID;
request.AgentData.SessionID = Simulator.Client.Self.SessionID;
request.ParcelData.LocalID = this.LocalID;
request.ParcelData.AuthBuyerID = this.AuthBuyerID;
request.ParcelData.Category = (byte)this.Category;
request.ParcelData.Desc = Utils.StringToBytes(this.Desc);
request.ParcelData.GroupID = this.GroupID;
request.ParcelData.LandingType = this.LandingType;
request.ParcelData.MediaAutoScale = this.Media.MediaAutoScale;
request.ParcelData.MediaID = this.Media.MediaID;
request.ParcelData.MediaURL = Utils.StringToBytes(this.Media.MediaURL);
request.ParcelData.MusicURL = Utils.StringToBytes(this.MusicURL);
request.ParcelData.Name = Utils.StringToBytes(this.Name);
if (wantReply) request.ParcelData.Flags = 1;
request.ParcelData.ParcelFlags = (uint)this.Flags;
request.ParcelData.PassHours = this.PassHours;
request.ParcelData.PassPrice = this.PassPrice;
request.ParcelData.SalePrice = this.SalePrice;
request.ParcelData.SnapshotID = this.SnapshotID;
request.ParcelData.UserLocation = this.UserLocation;
request.ParcelData.UserLookAt = this.UserLookAt;
Simulator.Client.Network.SendPacket(request, Simulator);
UpdateOtherCleanTime();
}
///
/// Set Autoreturn time
///
public void UpdateOtherCleanTime()
{
ParcelSetOtherCleanTimePacket request = new ParcelSetOtherCleanTimePacket();
request.AgentData.AgentID = Simulator.Client.Self.AgentID;
request.AgentData.SessionID = Simulator.Client.Self.SessionID;
request.ParcelData.LocalID = this.LocalID;
request.ParcelData.OtherCleanTime = this.OtherCleanTime;
Simulator.Client.Network.SendPacket(request, Simulator);
}
}
#endregion Parcel Class
///
/// Parcel (subdivided simulator lots) subsystem
///
public class ParcelManager
{
#region Enums
///
/// Type of return to use when returning objects from a parcel
///
public enum ObjectReturnType : uint
{
///
None = 0,
/// Return objects owned by parcel owner
Owner = 1 << 1,
/// Return objects set to group
Group = 1 << 2,
/// Return objects not owned by parcel owner or set to group
Other = 1 << 3,
/// Return a specific list of objects on parcel
List = 1 << 4,
/// Return objects that are marked for-sale
Sell = 1 << 5
}
///
/// Blacklist/Whitelist flags used in parcels Access List
///
public enum ParcelAccessFlags : uint
{
/// Agent is denied access
NoAccess = 0,
/// Agent is granted access
Access = 1
}
///
/// The result of a request for parcel properties
///
public enum ParcelResult : int
{
/// No matches were found for the request
NoData = -1,
/// Request matched a single parcel
Single = 0,
/// Request matched multiple parcels
Multiple = 1
}
///
/// Flags used in the ParcelAccessListRequest packet to specify whether
/// we want the access list (whitelist), ban list (blacklist), or both
///
[Flags]
public enum AccessList : uint
{
/// Request the access list
Access = 1 << 0,
/// Request the ban list
Ban = 1 << 1,
/// Request both the access list and ban list
Both = Access | Ban
}
///
/// Simulator sent Sequence IDs for ParcelPropertiesReply packets (sent when avatar tries to cross
/// parcel border)
///
public enum SequenceStatus : int
{
/// Parcel currently selected
ParcelSelected = -10000,
/// Parcel restricted to group avatar not member of
Collision_Not_In_Group = -20000,
/// Avatar banned from parcel
Collision_Banned = -30000,
/// Parcel restricted to access list in which avatar is not on.
Collision_Not_On_AccessList = -40000,
/// response to hovered over parcel
Hovered_Over_Parcel = -50000
}
///
///
///
public enum TerraformAction : byte
{
///
Level = 0,
///
Raise = 1,
///
Lower = 2,
///
Smooth = 3,
///
Noise = 4,
///
Revert = 5
}
///
///
///
public enum TerraformBrushSize : byte
{
///
Small = 1,
///
Medium = 2,
///
Large = 4
}
///
/// Reasons agent is denied access to a parcel on the simulator
///
public enum AccessDeniedReason : byte
{
/// Agent is not denied, access is granted
NotDenied = 0,
/// Agent is not a member of the group set for the parcel, or which owns the parcel
NotInGroup = 1,
/// Agent is not on the parcels specific allow list
NotOnAllowList = 2,
/// Agent is on the parcels ban list
BannedFromParcel = 3,
/// Unknown
NoAccess = 4,
/// Agent is not age verified and parcel settings deny access to non age verified avatars
NotAgeVerified = 5
}
#endregion Enums
#region Structs
///
/// Parcel Accesslist
///
public struct ParcelAccessEntry
{
/// Agents
public UUID AgentID;
///
public DateTime Time;
/// Flag to Permit access to agent, or ban agent from parcel
public AccessList Flags;
}
///
/// Owners of primitives on parcel
///
public struct ParcelPrimOwners
{
/// Prim Owners
public UUID OwnerID;
/// True of owner is group
public bool IsGroupOwned;
/// Total count of prims owned by OwnerID
public int Count;
/// true of OwnerID is currently online and is not a group
public bool OnlineStatus;
}
#endregion Structs
#region Delegates
///
///
///
/// UUID of the requested parcel
/// Simulator-local ID of the requested parcel
/// Dwell value of the requested parcel
public delegate void ParcelDwellCallback(UUID parcelID, int localID, float dwell);
///
///
///
///
public delegate void ParcelInfoCallback(ParcelInfo parcel);
///
///
///
/// Full properties for a single parcel. If result
/// is NoData this will be incomplete or incorrect data
/// Success of the query
/// User-assigned identifier for the query
/// User-assigned boolean for the query
public delegate void ParcelPropertiesCallback(Parcel parcel, ParcelResult result, int sequenceID, bool snapSelection);
///
///
///
/// simulator parcel is in
///
///
///
///
public delegate void ParcelAccessListReplyCallback(Simulator simulator, int sequenceID, int localID, uint flags, List accessEntries);
///
/// Responses to a request for prim owners on a parcel
///
/// simulator parcel is in
/// List containing details or prim ownership
public delegate void ParcelObjectOwnersListReplyCallback(Simulator simulator, List primOwners);
///
/// Fired when all parcels are downloaded from simulator
///
/// Simulator the parcel is in
/// Read-only dictionary containing parcel details for the simulator
/// 64,64 array containing sim position to localID mapping
public delegate void SimParcelsDownloaded(Simulator simulator, InternalDictionary simParcels, int[,] parcelMap);
///
/// Fired in response to SelectParcelObjects
///
/// simulator the objects are in
/// Local IDs of the selected objects
/// If true, list is start of a new selection
public delegate void ForceSelectObjects(Simulator simulator, List objectIDs, bool resetList);
///
/// Fired when a ParcelMediaUpdate packet is received, this occurs when the media on the parcel an avatar
/// is over changes
///
/// A reference to the simulator object
/// A struct containing updated media information
public delegate void ParcelMediaUpdateReplyCallback(Simulator simulator, ParcelMedia media);
#endregion Delegates
#region Events
/// Fired when a is received,
/// in response to a
public event ParcelDwellCallback OnParcelDwell;
/// Fired when a is received,
/// in response to a
public event ParcelInfoCallback OnParcelInfo;
/// Fired when a ParcelProperties Packet is received over the subsystem,
/// in response to a
public event ParcelPropertiesCallback OnParcelProperties;
/// Fired when a is received,
/// in response to a
public event ParcelAccessListReplyCallback OnAccessListReply;
/// Fired when the Agent receives a ,
/// in response to
public event ParcelObjectOwnersListReplyCallback OnPrimOwnersListReply;
/// Fired when the simulator parcel dictionary is populated in response
/// to a request
public event SimParcelsDownloaded OnSimParcelsDownloaded;
/// Fired when the Agent receives a ,
/// in response to a request
public event ForceSelectObjects OnParcelSelectedObjects;
/// Fired when the Agent receives a which
/// occurs when the parcel media information is changed for the current parcel the Agent is over
public event ParcelMediaUpdateReplyCallback OnParcelMediaUpdate;
#endregion Events
private GridClient Client;
#region Public Methods
///
/// Default constructor
///
/// A reference to the GridClient object
public ParcelManager(GridClient client)
{
Client = client;
// Setup the callbacks
Client.Network.RegisterCallback(PacketType.ParcelInfoReply, new NetworkManager.PacketCallback(ParcelInfoReplyHandler));
// UDP packet handler
Client.Network.RegisterCallback(PacketType.ParcelProperties, new NetworkManager.PacketCallback(ParcelPropertiesHandler));
// CAPS packet handler, to allow for Media Data not contained in the message template
Client.Network.RegisterEventCallback("ParcelProperties", new Caps.EventQueueCallback(ParcelPropertiesReplyHandler));
Client.Network.RegisterCallback(PacketType.ParcelDwellReply, new NetworkManager.PacketCallback(ParcelDwellReplyHandler));
Client.Network.RegisterCallback(PacketType.ParcelAccessListReply, new NetworkManager.PacketCallback(ParcelAccessListReplyHandler));
Client.Network.RegisterCallback(PacketType.ParcelObjectOwnersReply, new NetworkManager.PacketCallback(ParcelObjectOwnersReplyHandler));
Client.Network.RegisterCallback(PacketType.ForceObjectSelect, new NetworkManager.PacketCallback(SelectParcelObjectsReplyHandler));
Client.Network.RegisterCallback(PacketType.ParcelMediaUpdate, new NetworkManager.PacketCallback(ParcelMediaUpdateHandler));
}
///
/// Request basic information for a single parcel
///
/// Simulator-local ID of the parcel
public void InfoRequest(UUID parcelID)
{
ParcelInfoRequestPacket request = new ParcelInfoRequestPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.Data.ParcelID = parcelID;
Client.Network.SendPacket(request);
}
///
/// Request properties of a single parcel
///
/// Simulator containing the parcel
/// Simulator-local ID of the parcel
/// An arbitrary integer that will be returned
/// with the ParcelProperties reply, useful for distinguishing between
/// multiple simultaneous requests
public void PropertiesRequest(Simulator simulator, int localID, int sequenceID)
{
ParcelPropertiesRequestByIDPacket request = new ParcelPropertiesRequestByIDPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.ParcelData.LocalID = localID;
request.ParcelData.SequenceID = sequenceID;
Client.Network.SendPacket(request, simulator);
}
///
/// Request the access list for a single parcel
///
/// Simulator containing the parcel
/// Simulator-local ID of the parcel
/// An arbitrary integer that will be returned
/// with the ParcelAccessList reply, useful for distinguishing between
/// multiple simultaneous requests
///
public void AccessListRequest(Simulator simulator, int localID, AccessList flags, int sequenceID)
{
ParcelAccessListRequestPacket request = new ParcelAccessListRequestPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.Data.LocalID = localID;
request.Data.Flags = (uint)flags;
request.Data.SequenceID = sequenceID;
Client.Network.SendPacket(request, simulator);
}
///
/// Request properties of parcels using a bounding box selection
///
/// Simulator containing the parcel
/// Northern boundary of the parcel selection
/// Eastern boundary of the parcel selection
/// Southern boundary of the parcel selection
/// Western boundary of the parcel selection
/// An arbitrary integer that will be returned
/// with the ParcelProperties reply, useful for distinguishing between
/// different types of parcel property requests
/// A boolean that is returned with the
/// ParcelProperties reply, useful for snapping focus to a single
/// parcel
public void PropertiesRequest(Simulator simulator, float north, float east, float south, float west,
int sequenceID, bool snapSelection)
{
ParcelPropertiesRequestPacket request = new ParcelPropertiesRequestPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.ParcelData.North = north;
request.ParcelData.East = east;
request.ParcelData.South = south;
request.ParcelData.West = west;
request.ParcelData.SequenceID = sequenceID;
request.ParcelData.SnapSelection = snapSelection;
Client.Network.SendPacket(request, simulator);
}
///
/// Request all simulator parcel properties (used for populating the Simulator.Parcels
/// dictionary)
///
/// Simulator to request parcels from (must be connected)
public void RequestAllSimParcels(Simulator simulator)
{
RequestAllSimParcels(simulator, false, 200);
}
///
/// Request all simulator parcel properties (used for populating the Simulator.Parcels
/// dictionary)
///
/// Simulator to request parcels from (must be connected)
/// If TRUE, will force a full refresh
/// Number of milliseconds to pause in between each request
public void RequestAllSimParcels(Simulator simulator, bool refresh, int msDelay)
{
if (refresh)
{
//lock (simulator.ParcelMap)
for (int y = 0; y < 64; y++)
for (int x = 0; x < 64; x++)
simulator.ParcelMap[y, x] = 0;
}
Thread th = new Thread(delegate()
{
int count = 0, y, x;
for (y = 0; y < 64; y++)
{
for (x = 0; x < 64; x++)
{
if (!Client.Network.Connected)
return;
if (simulator.ParcelMap[y, x] == 0)
{
Client.Parcels.PropertiesRequest(simulator,
(y + 1) * 4.0f, (x + 1) * 4.0f,
y * 4.0f, x * 4.0f, 0, false);
// Simulators can be sliced up in 4x4 chunks, but even the smallest
// parcel is 16x16. There's no reason to send out 4096 packets. If
// we sleep for a little while here it is likely the response
// packet will come back and nearby parcel information is filled in
// so we can skip a lot of unnecessary sends
Thread.Sleep(100);
++count;
}
}
}
Logger.Log(String.Format(
"Requested full simulator parcel information. Sent {0} parcel requests. Current outgoing queue: {1}",
count, Client.Network.OutboxCount), Helpers.LogLevel.Info);
});
th.Start();
}
///
/// Request the dwell value for a parcel
///
/// Simulator containing the parcel
/// Simulator-local ID of the parcel
public void DwellRequest(Simulator simulator, int localID)
{
ParcelDwellRequestPacket request = new ParcelDwellRequestPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.Data.LocalID = localID;
request.Data.ParcelID = UUID.Zero; // Not used by clients
Client.Network.SendPacket(request, simulator);
}
///
/// Send a request to Purchase a parcel of land
///
/// The Simulator the parcel is located in
/// The parcels region specific local ID
/// true if this parcel is being purchased by a group
/// The groups
/// true to remove tier contribution if purchase is successful
/// The parcels size
/// The purchase price of the parcel
///
public void Buy(Simulator simulator, int localID, bool forGroup, UUID groupID,
bool removeContribution, int parcelArea, int parcelPrice)
{
ParcelBuyPacket request = new ParcelBuyPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.Data.Final = true;
request.Data.GroupID = groupID;
request.Data.LocalID = localID;
request.Data.IsGroupOwned = forGroup;
request.Data.RemoveContribution = removeContribution;
request.ParcelData.Area = parcelArea;
request.ParcelData.Price = parcelPrice;
Client.Network.SendPacket(request, simulator);
}
///
/// Reclaim a parcel of land
///
/// The simulator the parcel is in
/// The parcels region specific local ID
public void Reclaim(Simulator simulator, int localID)
{
ParcelReclaimPacket request = new ParcelReclaimPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.Data.LocalID = localID;
Client.Network.SendPacket(request, simulator);
}
///
/// Deed a parcel to a group
///
/// The simulator the parcel is in
/// The parcels region specific local ID
/// The groups
public void DeedToGroup(Simulator simulator, int localID, UUID groupID)
{
ParcelDeedToGroupPacket request = new ParcelDeedToGroupPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.Data.LocalID = localID;
request.Data.GroupID = groupID;
Client.Network.SendPacket(request, simulator);
}
///
/// Request prim owners of a parcel of land.
///
/// Simulator parcel is in
/// The parcels region specific local ID
public void ObjectOwnersRequest(Simulator simulator, int localID)
{
ParcelObjectOwnersRequestPacket request = new ParcelObjectOwnersRequestPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.ParcelData.LocalID = localID;
Client.Network.SendPacket(request, simulator);
}
///
/// Return objects from a parcel
///
/// Simulator parcel is in
/// The parcels region specific local ID
/// the type of objects to return,
/// A list containing object owners s to return
public void ReturnObjects(Simulator simulator, int localID, ObjectReturnType type, List ownerIDs)
{
ParcelReturnObjectsPacket request = new ParcelReturnObjectsPacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.ParcelData.LocalID = localID;
request.ParcelData.ReturnType = (uint)type;
// A single null TaskID is (not) used for parcel object returns
request.TaskIDs = new ParcelReturnObjectsPacket.TaskIDsBlock[1];
request.TaskIDs[0] = new ParcelReturnObjectsPacket.TaskIDsBlock();
request.TaskIDs[0].TaskID = UUID.Zero;
// Convert the list of owner UUIDs to packet blocks if a list is given
if (ownerIDs != null)
{
request.OwnerIDs = new ParcelReturnObjectsPacket.OwnerIDsBlock[ownerIDs.Count];
for (int i = 0; i < ownerIDs.Count; i++)
{
request.OwnerIDs[i] = new ParcelReturnObjectsPacket.OwnerIDsBlock();
request.OwnerIDs[i].OwnerID = ownerIDs[i];
}
}
else
{
request.OwnerIDs = new ParcelReturnObjectsPacket.OwnerIDsBlock[0];
}
Client.Network.SendPacket(request, simulator);
}
///
/// Subdivide (split) a parcel
///
///
///
///
///
///
public void ParcelSubdivide(Simulator simulator, float west, float south, float east, float north)
{
ParcelDividePacket divide = new ParcelDividePacket();
divide.AgentData.AgentID = Client.Self.AgentID;
divide.AgentData.SessionID = Client.Self.SessionID;
divide.ParcelData.East = east;
divide.ParcelData.North = north;
divide.ParcelData.South = south;
divide.ParcelData.West = west;
Client.Network.SendPacket(divide, simulator);
}
///
/// Join two parcels of land creating a single parcel
///
///
///
///
///
///
public void ParcelJoin(Simulator simulator, float west, float south, float east, float north)
{
ParcelJoinPacket join = new ParcelJoinPacket();
join.AgentData.AgentID = Client.Self.AgentID;
join.AgentData.SessionID = Client.Self.SessionID;
join.ParcelData.East = east;
join.ParcelData.North = north;
join.ParcelData.South = south;
join.ParcelData.West = west;
Client.Network.SendPacket(join, simulator);
}
///
/// Get a parcels LocalID
///
/// Simulator parcel is in
/// llVector3 position in simulator (Z not used)
/// 0 on failure, or parcel LocalID on success.
/// A call to Parcels.RequestAllSimParcels is required to populate map and
/// dictionary.
public int GetParcelLocalID(Simulator simulator, Vector3 position)
{
return simulator.ParcelMap[(byte)position.Y / 4, (byte)position.X / 4];
}
///
/// Terraform (raise, lower, etc) an area or whole parcel of land
///
/// Simulator land area is in.
/// LocalID of parcel, or -1 if using bounding box
/// From Enum, Raise, Lower, Level, Smooth, Etc.
/// Size of area to modify
/// true on successful request sent.
/// Settings.STORE_LAND_PATCHES must be true,
/// Parcel information must be downloaded using RequestAllSimParcels()
public bool Terraform(Simulator simulator, int localID, TerraformAction action, TerraformBrushSize brushSize)
{
return Terraform(simulator, localID, 0f, 0f, 0f, 0f, action, brushSize, 1);
}
///
/// Terraform (raise, lower, etc) an area or whole parcel of land
///
/// Simulator land area is in.
/// west border of area to modify
/// south border of area to modify
/// east border of area to modify
/// north border of area to modify
/// From Enum, Raise, Lower, Level, Smooth, Etc.
/// Size of area to modify
/// true on successful request sent.
/// Settings.STORE_LAND_PATCHES must be true,
/// Parcel information must be downloaded using RequestAllSimParcels()
public bool Terraform(Simulator simulator, float west, float south, float east, float north,
TerraformAction action, TerraformBrushSize brushSize)
{
return Terraform(simulator, -1, west, south, east, north, action, brushSize, 1);
}
///
/// Terraform (raise, lower, etc) an area or whole parcel of land
///
/// Simulator land area is in.
/// LocalID of parcel, or -1 if using bounding box
/// west border of area to modify
/// south border of area to modify
/// east border of area to modify
/// north border of area to modify
/// From Enum, Raise, Lower, Level, Smooth, Etc.
/// Size of area to modify
/// How many meters + or - to lower, 1 = 1 meter
/// true on successful request sent.
/// Settings.STORE_LAND_PATCHES must be true,
/// Parcel information must be downloaded using RequestAllSimParcels()
public bool Terraform(Simulator simulator, int localID, float west, float south, float east, float north,
TerraformAction action, TerraformBrushSize brushSize, int seconds)
{
float height = 0f;
int x, y;
if (localID == -1)
{
x = (int)east - (int)west / 2;
y = (int)north - (int)south / 2;
}
else
{
Parcel p;
if (!simulator.Parcels.TryGetValue(localID, out p))
{
Logger.Log(String.Format("Can't find parcel {0} in simulator {1}", localID, simulator),
Helpers.LogLevel.Warning, Client);
return false;
}
x = (int)p.AABBMax.X - (int)p.AABBMin.X / 2;
y = (int)p.AABBMax.Y - (int)p.AABBMin.Y / 2;
}
if (!Client.Terrain.TerrainHeightAtPoint(simulator.Handle, x, y, out height))
{
Logger.Log("Land Patch not stored for location", Helpers.LogLevel.Warning, Client);
return false;
}
Terraform(simulator, localID, west, south, east, north, action, brushSize, seconds, height);
return true;
}
///
/// Terraform (raise, lower, etc) an area or whole parcel of land
///
/// Simulator land area is in.
/// LocalID of parcel, or -1 if using bounding box
/// west border of area to modify
/// south border of area to modify
/// east border of area to modify
/// north border of area to modify
/// From Enum, Raise, Lower, Level, Smooth, Etc.
/// Size of area to modify
/// How many meters + or - to lower, 1 = 1 meter
/// Height at which the terraform operation is acting at
public void Terraform(Simulator simulator, int localID, float west, float south, float east, float north,
TerraformAction action, TerraformBrushSize brushSize, int seconds, float height)
{
ModifyLandPacket land = new ModifyLandPacket();
land.AgentData.AgentID = Client.Self.AgentID;
land.AgentData.SessionID = Client.Self.SessionID;
land.ModifyBlock.Action = (byte)action;
land.ModifyBlock.BrushSize = (byte)brushSize;
land.ModifyBlock.Seconds = seconds;
land.ModifyBlock.Height = height;
land.ParcelData[0] = new ModifyLandPacket.ParcelDataBlock();
land.ParcelData[0].LocalID = localID;
land.ParcelData[0].West = west;
land.ParcelData[0].South = south;
land.ParcelData[0].East = east;
land.ParcelData[0].North = north;
Client.Network.SendPacket(land, simulator);
}
///
/// Sends a request to the simulator to return a list of objects owned by specific owners
///
/// Simulator local ID of parcel
/// Owners, Others, Etc
/// List containing keys of avatars objects to select;
/// if List is null will return Objects of type selectType
/// Response data is returned in the event
public void SelectObjects(int localID, ObjectReturnType selectType, UUID ownerID)
{
if (OnParcelSelectedObjects != null)
{
ParcelSelectObjectsPacket select = new ParcelSelectObjectsPacket();
select.AgentData.AgentID = Client.Self.AgentID;
select.AgentData.SessionID = Client.Self.SessionID;
select.ParcelData.LocalID = localID;
select.ParcelData.ReturnType = (uint)selectType;
select.ReturnIDs = new ParcelSelectObjectsPacket.ReturnIDsBlock[1];
select.ReturnIDs[0] = new ParcelSelectObjectsPacket.ReturnIDsBlock();
select.ReturnIDs[0].ReturnID = ownerID;
Client.Network.SendPacket(select);
}
}
///
/// Eject and optionally ban a user from a parcel
///
/// target key of avatar to eject
/// true to also ban target
public void EjectUser(UUID targetID, bool ban)
{
EjectUserPacket eject = new EjectUserPacket();
eject.AgentData.AgentID = Client.Self.AgentID;
eject.AgentData.SessionID = Client.Self.SessionID;
eject.Data.TargetID = targetID;
if (ban) eject.Data.Flags = 1;
else eject.Data.Flags = 0;
Client.Network.SendPacket(eject);
}
///
/// Freeze or unfreeze an avatar over your land
///
/// target key to freeze
/// true to freeze, false to unfreeze
public void FreezeUser(UUID targetID, bool freeze)
{
FreezeUserPacket frz = new FreezeUserPacket();
frz.AgentData.AgentID = Client.Self.AgentID;
frz.AgentData.SessionID = Client.Self.SessionID;
frz.Data.TargetID = targetID;
if (freeze) frz.Data.Flags = 0;
else frz.Data.Flags = 1;
Client.Network.SendPacket(frz);
}
///
/// Abandon a parcel of land
///
/// Simulator parcel is in
/// Simulator local ID of parcel
public void ReleaseParcel(Simulator simulator, int localID)
{
ParcelReleasePacket abandon = new ParcelReleasePacket();
abandon.AgentData.AgentID = Client.Self.AgentID;
abandon.AgentData.SessionID = Client.Self.SessionID;
abandon.Data.LocalID = localID;
Client.Network.SendPacket(abandon, simulator);
}
#endregion Public Methods
#region Packet Handlers
private void ParcelDwellReplyHandler(Packet packet, Simulator simulator)
{
if (OnParcelDwell != null || Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL == true)
{
ParcelDwellReplyPacket dwell = (ParcelDwellReplyPacket)packet;
lock (simulator.Parcels.Dictionary)
{
if (simulator.Parcels.Dictionary.ContainsKey(dwell.Data.LocalID))
{
Parcel parcel = simulator.Parcels.Dictionary[dwell.Data.LocalID];
parcel.Dwell = dwell.Data.Dwell;
simulator.Parcels.Dictionary[dwell.Data.LocalID] = parcel;
}
}
if (OnParcelDwell != null)
{
try { OnParcelDwell(dwell.Data.ParcelID, dwell.Data.LocalID, dwell.Data.Dwell); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
}
private void ParcelInfoReplyHandler(Packet packet, Simulator simulator)
{
if (OnParcelInfo != null)
{
ParcelInfoReplyPacket info = (ParcelInfoReplyPacket)packet;
ParcelInfo parcelInfo = new ParcelInfo();
parcelInfo.ActualArea = info.Data.ActualArea;
parcelInfo.AuctionID = info.Data.AuctionID;
parcelInfo.BillableArea = info.Data.BillableArea;
parcelInfo.Description = Utils.BytesToString(info.Data.Desc);
parcelInfo.Dwell = info.Data.Dwell;
parcelInfo.GlobalX = info.Data.GlobalX;
parcelInfo.GlobalY = info.Data.GlobalY;
parcelInfo.GlobalZ = info.Data.GlobalZ;
parcelInfo.ID = info.Data.ParcelID;
parcelInfo.Mature = ((info.Data.Flags & 1) != 0) ? true : false;
parcelInfo.Name = Utils.BytesToString(info.Data.Name);
parcelInfo.OwnerID = info.Data.OwnerID;
parcelInfo.SalePrice = info.Data.SalePrice;
parcelInfo.SimName = Utils.BytesToString(info.Data.SimName);
parcelInfo.SnapshotID = info.Data.SnapshotID;
try { OnParcelInfo(parcelInfo); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
///
/// ParcelProperties replies sent over CAPS
///
/// Not used (will always be ParcelProperties)
/// LLSD Structured data
/// Object representing simulator
private void ParcelPropertiesReplyHandler(string capsKey, LLSD llsd, Simulator simulator)
{
if (OnParcelProperties != null || Client.Settings.PARCEL_TRACKING == true)
{
LLSDMap map = (LLSDMap)llsd;
LLSDMap parcelDataBlock = (LLSDMap)(((LLSDArray)map["ParcelData"])[0]);
LLSDMap ageVerifyBlock = (LLSDMap)(((LLSDArray)map["AgeVerificationBlock"])[0]);
LLSDMap mediaDataBlock = (LLSDMap)(((LLSDArray)map["MediaData"])[0]);
Parcel parcel = new Parcel(simulator, parcelDataBlock["LocalID"].AsInteger());
parcel.AABBMax = ((LLSDArray)parcelDataBlock["AABBMax"]).AsVector3();
parcel.AABBMin = ((LLSDArray)parcelDataBlock["AABBMin"]).AsVector3();
parcel.Area = parcelDataBlock["Area"].AsInteger();
parcel.AuctionID = (uint)parcelDataBlock["AuctionID"].AsInteger();
parcel.AuthBuyerID = parcelDataBlock["AuthBuyerID"].AsUUID();
parcel.Bitmap = parcelDataBlock["Bitmap"].AsBinary();
parcel.Category = (Parcel.ParcelCategory)parcelDataBlock["Category"].AsInteger();
parcel.ClaimDate = Utils.UnixTimeToDateTime((uint)parcelDataBlock["ClaimDate"].AsInteger());
parcel.ClaimPrice = parcelDataBlock["ClaimPrice"].AsInteger();
parcel.Desc = parcelDataBlock["Desc"].AsString();
// TODO: this probably needs to happen when the packet is deserialized.
byte[] bytes = parcelDataBlock["ParcelFlags"].AsBinary();
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
parcel.Flags = (Parcel.ParcelFlags)BitConverter.ToUInt32(bytes, 0);
parcel.GroupID = parcelDataBlock["GroupID"].AsUUID();
parcel.GroupPrims = parcelDataBlock["GroupPrims"].AsInteger();
parcel.IsGroupOwned = parcelDataBlock["IsGroupOwned"].AsBoolean();
parcel.LandingType = (byte)parcelDataBlock["LandingType"].AsInteger();
parcel.LocalID = parcelDataBlock["LocalID"].AsInteger();
parcel.MaxPrims = parcelDataBlock["MaxPrims"].AsInteger();
parcel.Media.MediaAutoScale = (byte)parcelDataBlock["MediaAutoScale"].AsInteger();
parcel.Media.MediaID = parcelDataBlock["MediaID"].AsUUID();
parcel.Media.MediaURL = parcelDataBlock["MediaURL"].AsString();
parcel.MusicURL = parcelDataBlock["MusicURL"].AsString();
parcel.Name = parcelDataBlock["Name"].AsString();
parcel.OtherCleanTime = parcelDataBlock["OtherCleanTime"].AsInteger();
parcel.OtherCount = parcelDataBlock["OtherCount"].AsInteger();
parcel.OtherPrims = parcelDataBlock["OtherPrims"].AsInteger();
parcel.OwnerID = parcelDataBlock["OwnerID"].AsUUID();
parcel.OwnerPrims = parcelDataBlock["OwnerPrims"].AsInteger();
parcel.ParcelPrimBonus = (float)parcelDataBlock["ParcelPrimBonus"].AsReal();
parcel.PassHours = (float)parcelDataBlock["PassHours"].AsReal();
parcel.PassPrice = parcelDataBlock["PassPrice"].AsInteger();
parcel.PublicCount = parcelDataBlock["PublicCount"].AsInteger();
parcel.RegionDenyAgeUnverified = ageVerifyBlock["RegionDenyAgeUnverified"].AsBoolean();
parcel.RegionDenyAnonymous = parcelDataBlock["RegionDenyAnonymous"].AsBoolean();
parcel.RegionPushOverride = parcelDataBlock["RegionPushOverride"].AsBoolean();
parcel.RentPrice = parcelDataBlock["RentPrice"].AsInteger();
parcel.RequestResult = parcelDataBlock["RequestResult"].AsInteger();
parcel.SalePrice = parcelDataBlock["SalePrice"].AsInteger();
parcel.SelectedPrims = parcelDataBlock["SelectedPrims"].AsInteger();
parcel.SelfCount = parcelDataBlock["SelfCount"].AsInteger();
parcel.SequenceID = parcelDataBlock["SequenceID"].AsInteger();
parcel.Simulator = simulator;
parcel.SimWideMaxPrims = parcelDataBlock["SimWideMaxPrims"].AsInteger();
parcel.SimWideTotalPrims = parcelDataBlock["SimWideTotalPrims"].AsInteger();
parcel.SnapSelection = parcelDataBlock["SnapSelection"].AsBoolean();
parcel.SnapshotID = parcelDataBlock["SnapshotID"].AsUUID();
parcel.Status = (Parcel.ParcelStatus)parcelDataBlock["Status"].AsInteger();
parcel.TotalPrims = parcelDataBlock["TotalPrims"].AsInteger();
parcel.UserLocation = ((LLSDArray)parcelDataBlock["UserLocation"]).AsVector3();
parcel.UserLookAt = ((LLSDArray)parcelDataBlock["UserLookAt"]).AsVector3();
parcel.Media.MediaDesc = mediaDataBlock["MediaDesc"].AsString();
parcel.Media.MediaHeight = mediaDataBlock["MediaHeight"].AsInteger();
parcel.Media.MediaWidth = mediaDataBlock["MediaWidth"].AsInteger();
parcel.Media.MediaLoop = mediaDataBlock["MediaLoop"].AsBoolean();
parcel.Media.MediaType = mediaDataBlock["MediaType"].AsString();
parcel.ObscureMedia = mediaDataBlock["ObscureMedia"].AsBoolean();
parcel.ObscureMusic = mediaDataBlock["ObscureMusic"].AsBoolean();
if (Client.Settings.PARCEL_TRACKING)
{
lock (simulator.Parcels.Dictionary)
simulator.Parcels.Dictionary[parcel.LocalID] = parcel;
int y, x, index, bit;
for (y = 0; y < simulator.ParcelMap.GetLength(0); y++)
{
for (x = 0; x < simulator.ParcelMap.GetLength(1); x++)
{
if (simulator.ParcelMap[y, x] == 0)
{
index = (y * 64) + x;
bit = index % 8;
index >>= 3;
if ((parcel.Bitmap[index] & (1 << bit)) != 0)
simulator.ParcelMap[y, x] = parcel.LocalID;
}
}
}
}
// auto request acl, will be stored in parcel tracking dictionary if enabled
if (Client.Settings.ALWAYS_REQUEST_PARCEL_ACL)
Client.Parcels.AccessListRequest(simulator, parcel.LocalID,
AccessList.Both, parcel.SequenceID);
// auto request dwell, will be stored in parcel tracking dictionary if enables
if (Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL)
Client.Parcels.DwellRequest(simulator, parcel.LocalID);
// Fire the callback for parcel properties being received
if (OnParcelProperties != null)
{
try
{
OnParcelProperties(parcel, (ParcelResult)parcel.RequestResult,
parcel.SequenceID, parcel.SnapSelection);
}
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
// Check if all of the simulator parcels have been retrieved, if so fire another callback
if (OnSimParcelsDownloaded != null && simulator.IsParcelMapFull())
{
try { OnSimParcelsDownloaded(simulator, simulator.Parcels, simulator.ParcelMap); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
}
///
/// Parcel Properties reply handler for data that comes in over udp (deprecated)
///
///
///
private void ParcelPropertiesHandler(Packet packet, Simulator simulator)
{
if (OnParcelProperties != null || Client.Settings.PARCEL_TRACKING == true)
{
ParcelPropertiesPacket properties = (ParcelPropertiesPacket)packet;
Parcel parcel = new Parcel(simulator, properties.ParcelData.LocalID);
parcel.AABBMax = properties.ParcelData.AABBMax;
parcel.AABBMin = properties.ParcelData.AABBMin;
parcel.Area = properties.ParcelData.Area;
parcel.AuctionID = properties.ParcelData.AuctionID;
parcel.AuthBuyerID = properties.ParcelData.AuthBuyerID;
parcel.Bitmap = properties.ParcelData.Bitmap;
parcel.Category = (Parcel.ParcelCategory)(sbyte)properties.ParcelData.Category;
parcel.ClaimDate = Utils.UnixTimeToDateTime((uint)properties.ParcelData.ClaimDate);
// ClaimPrice seems to always be zero?
parcel.ClaimPrice = properties.ParcelData.ClaimPrice;
parcel.Desc = Utils.BytesToString(properties.ParcelData.Desc);
parcel.GroupID = properties.ParcelData.GroupID;
parcel.GroupPrims = properties.ParcelData.GroupPrims;
parcel.IsGroupOwned = properties.ParcelData.IsGroupOwned;
parcel.LandingType = properties.ParcelData.LandingType;
parcel.MaxPrims = properties.ParcelData.MaxPrims;
parcel.Media.MediaAutoScale = properties.ParcelData.MediaAutoScale;
parcel.Media.MediaID = properties.ParcelData.MediaID;
parcel.Media.MediaURL = Utils.BytesToString(properties.ParcelData.MediaURL);
parcel.MusicURL = Utils.BytesToString(properties.ParcelData.MusicURL);
parcel.Name = Utils.BytesToString(properties.ParcelData.Name);
parcel.OtherCleanTime = properties.ParcelData.OtherCleanTime;
parcel.OtherCount = properties.ParcelData.OtherCount;
parcel.OtherPrims = properties.ParcelData.OtherPrims;
parcel.OwnerID = properties.ParcelData.OwnerID;
parcel.OwnerPrims = properties.ParcelData.OwnerPrims;
parcel.Flags = (Parcel.ParcelFlags)properties.ParcelData.ParcelFlags;
parcel.ParcelPrimBonus = properties.ParcelData.ParcelPrimBonus;
parcel.PassHours = properties.ParcelData.PassHours;
parcel.PassPrice = properties.ParcelData.PassPrice;
parcel.PublicCount = properties.ParcelData.PublicCount;
parcel.RegionDenyAnonymous = properties.ParcelData.RegionDenyAnonymous;
parcel.RegionPushOverride = properties.ParcelData.RegionPushOverride;
parcel.RentPrice = properties.ParcelData.RentPrice;
parcel.SalePrice = properties.ParcelData.SalePrice;
parcel.SelectedPrims = properties.ParcelData.SelectedPrims;
parcel.SelfCount = properties.ParcelData.SelfCount;
parcel.SimWideMaxPrims = properties.ParcelData.SimWideMaxPrims;
parcel.SimWideTotalPrims = properties.ParcelData.SimWideTotalPrims;
parcel.SnapshotID = properties.ParcelData.SnapshotID;
parcel.Status = (Parcel.ParcelStatus)(sbyte)properties.ParcelData.Status;
parcel.TotalPrims = properties.ParcelData.TotalPrims;
parcel.UserLocation = properties.ParcelData.UserLocation;
parcel.UserLookAt = properties.ParcelData.UserLookAt;
parcel.RegionDenyAgeUnverified = properties.AgeVerificationBlock.RegionDenyAgeUnverified;
// store parcel in dictionary
if (Client.Settings.PARCEL_TRACKING)
{
lock (simulator.Parcels.Dictionary)
simulator.Parcels.Dictionary[parcel.LocalID] = parcel;
int y, x, index, bit;
for (y = 0; y < simulator.ParcelMap.GetLength(0); y++)
{
for (x = 0; x < simulator.ParcelMap.GetLength(1); x++)
{
if (simulator.ParcelMap[y, x] == 0)
{
index = (y * 64) + x;
bit = index % 8;
index >>= 3;
if ((parcel.Bitmap[index] & (1 << bit)) != 0)
simulator.ParcelMap[y, x] = parcel.LocalID;
}
}
}
}
// auto request acl, will be stored in parcel tracking dictionary if enabled
if (Client.Settings.ALWAYS_REQUEST_PARCEL_ACL)
Client.Parcels.AccessListRequest(simulator, properties.ParcelData.LocalID,
AccessList.Both, properties.ParcelData.SequenceID);
// auto request dwell, will be stored in parcel tracking dictionary if enables
if (Client.Settings.ALWAYS_REQUEST_PARCEL_DWELL)
Client.Parcels.DwellRequest(simulator, properties.ParcelData.LocalID);
// Fire the callback for parcel properties being received
if (OnParcelProperties != null)
{
try
{
OnParcelProperties(parcel, (ParcelResult)properties.ParcelData.RequestResult,
properties.ParcelData.SequenceID, properties.ParcelData.SnapSelection);
}
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
// Check if all of the simulator parcels have been retrieved, if so fire another callback
if (OnSimParcelsDownloaded != null && simulator.IsParcelMapFull())
{
try { OnSimParcelsDownloaded(simulator, simulator.Parcels, simulator.ParcelMap); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
}
///
///
///
///
///
protected void ParcelAccessListReplyHandler(Packet packet, Simulator simulator)
{
if (OnAccessListReply != null || Client.Settings.ALWAYS_REQUEST_PARCEL_ACL == true)
{
ParcelAccessListReplyPacket reply = (ParcelAccessListReplyPacket)packet;
List accessList = new List(reply.List.Length);
for (int i = 0; i < reply.List.Length; i++)
{
ParcelAccessEntry pae = new ParcelAccessEntry();
pae.AgentID = reply.List[i].ID;
pae.Flags = (AccessList)reply.List[i].Flags;
pae.Time = Utils.UnixTimeToDateTime((uint)reply.List[i].Time);
accessList.Add(pae);
}
lock (simulator.Parcels.Dictionary)
{
if (simulator.Parcels.Dictionary.ContainsKey(reply.Data.LocalID))
{
Parcel parcel = simulator.Parcels.Dictionary[reply.Data.LocalID];
parcel.AccessList = accessList;
simulator.Parcels.Dictionary[reply.Data.LocalID] = parcel;
}
}
if (OnAccessListReply != null)
{
try
{
OnAccessListReply(simulator, reply.Data.SequenceID, reply.Data.LocalID, reply.Data.Flags,
accessList);
}
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
}
private void ParcelObjectOwnersReplyHandler(Packet packet, Simulator simulator)
{
if (OnPrimOwnersListReply != null)
{
ParcelObjectOwnersReplyPacket reply = (ParcelObjectOwnersReplyPacket)packet;
List primOwners = new List();
for (int i = 0; i < reply.Data.Length; i++)
{
ParcelPrimOwners poe = new ParcelPrimOwners();
poe.OwnerID = reply.Data[i].OwnerID;
poe.IsGroupOwned = reply.Data[i].IsGroupOwned;
poe.Count = reply.Data[i].Count;
poe.OnlineStatus = reply.Data[i].OnlineStatus;
primOwners.Add(poe);
}
try { OnPrimOwnersListReply(simulator, primOwners); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void SelectParcelObjectsReplyHandler(Packet packet, Simulator simulator)
{
ForceObjectSelectPacket reply = (ForceObjectSelectPacket)packet;
List objectIDs = new List(reply.Data.Length);
for (int i = 0; i < reply.Data.Length; i++)
{
objectIDs.Add(reply.Data[i].LocalID);
}
if (OnParcelSelectedObjects != null)
{
try { OnParcelSelectedObjects(simulator, objectIDs, reply._Header.ResetList); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
private void ParcelMediaUpdateHandler(Packet packet, Simulator simulator)
{
ParcelMediaUpdatePacket reply = (ParcelMediaUpdatePacket)packet;
ParcelMedia media = new ParcelMedia();
media.MediaAutoScale = reply.DataBlock.MediaAutoScale;
media.MediaID = reply.DataBlock.MediaID;
media.MediaDesc = Utils.BytesToString(reply.DataBlockExtended.MediaDesc);
media.MediaHeight = reply.DataBlockExtended.MediaHeight;
media.MediaLoop = ((reply.DataBlockExtended.MediaLoop & 1) != 0) ? true : false;
media.MediaType = Utils.BytesToString(reply.DataBlockExtended.MediaType);
media.MediaWidth = reply.DataBlockExtended.MediaWidth;
media.MediaURL = Utils.BytesToString(reply.DataBlock.MediaURL);
if (OnParcelMediaUpdate != null)
{
try { OnParcelMediaUpdate(simulator, media); }
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
}
}
#endregion Packet Handlers
}
}