This patch adds parcel support in, there's still a tiny bit left to do (ParcelPropertiesUpdate, and adding more of the ParcelProperties into the Parcel class). ParcelDownload.cs is a sample application.
The primary function you will want to use is <region>.FillParcels() this will send out the packets to download the regions parcel properties, however this does take time. Sleeping 10 seconds after doing a FillParcels should be sufficient to ensure they are all downloaded. I'll add a event for when parcels are finished downloading in the next patch. There is currently no function to force libsl to redownload the parcel properties. Again, I'll add this into a future patch - so consider data only as up to date as since the first .FillParcels was called for that region. The old Parcel class needs to be seperated from the new one into a DirectoryParcel class as they both reference completely different sets of data, and the new functions (.Buy(), etc) will not work with 'Parcels' spawned outside of the Region.FillParcels() function, as they lack a LocalID. Important note: *** The parcel functions deal with potentially dangerous packets [such as buying land], libsl will not warn you before going up a tier, and has not been sufficiently tested to guaruntee it works correctly. Dont use these functions unless you know what you are doing, and are prepared to face the consequences of any malfunction or bug. -Adam
Index: Packets/ParcelPackets.cs =================================================================== --- Packets/ParcelPackets.cs (revision 106) +++ Packets/ParcelPackets.cs (working copy) @@ -67,5 +67,89 @@ return PacketBuilder.BuildPacket("ParcelBuy", protocol, blocks, Helpers.MSG_RELIABLE); } + + public static Packet ParcelDeedToGroup(ProtocolManager protocol, int localID, LLUUID groupID, + LLUUID agentID, LLUUID sessionID) + { + Hashtable blocks = new Hashtable(); + Hashtable fields = new Hashtable(); + + fields["LocalID"] = localID; + fields["GroupID"] = groupID; + blocks[fields] = "Data"; + + fields = new Hashtable(); + fields["AgentID"] = agentID; + fields["SessionID"] = sessionID; + blocks[fields] = "AgentData"; + + return PacketBuilder.BuildPacket("ParcelDeedToGroup", protocol, blocks, Helpers.MSG_RELIABLE); + } + + public static Packet ParcelReclaim(ProtocolManager protocol, int localID, + LLUUID agentID, LLUUID sessionID) + { + Hashtable blocks = new Hashtable(); + Hashtable fields = new Hashtable(); + + fields["LocalID"] = localID; + blocks[fields] = "Data"; + + fields = new Hashtable(); + fields["AgentID"] = agentID; + fields["SessionID"] = sessionID; + blocks[fields] = "AgentData"; + + return PacketBuilder.BuildPacket("ParcelReclaim", protocol, blocks, Helpers.MSG_RELIABLE); + } + + public static Packet ParcelRelease(ProtocolManager protocol, int localID, + LLUUID agentID, LLUUID sessionID) + { + Hashtable blocks = new Hashtable(); + Hashtable fields = new Hashtable(); + + fields["LocalID"] = localID; + blocks[fields] = "Data"; + + fields = new Hashtable(); + fields["AgentID"] = agentID; + fields["SessionID"] = sessionID; + blocks[fields] = "AgentData"; + + return PacketBuilder.BuildPacket("ParcelRelease", protocol, blocks, Helpers.MSG_RELIABLE); + } + + public static Packet ParcelPropertiesRequest(ProtocolManager protocol, LLUUID agentID, int sequenceID, + float west, float south, float east, float north, bool snapSelection) + { + Hashtable blocks = new Hashtable(); + Hashtable fields = new Hashtable(); + + fields["AgentID"] = agentID; + fields["SequenceID"] = sequenceID; + fields["West"] = west; + fields["South"] = south; + fields["East"] = east; + fields["North"] = north; + fields["SnapSelection"] = snapSelection; + + blocks[fields] = "ParcelData"; + return PacketBuilder.BuildPacket("ParcelPropertiesRequest", protocol, blocks, Helpers.MSG_RELIABLE); + } + + public static Packet ParcelPropertiesRequestByID(ProtocolManager protocol, LLUUID agentID, int sequenceID, + int localID) + { + Hashtable blocks = new Hashtable(); + Hashtable fields = new Hashtable(); + + fields["AgentID"] = agentID; + fields["SequenceID"] = sequenceID; + fields["LocalID"] = localID; + + blocks[fields] = "ParcelData"; + return PacketBuilder.BuildPacket("ParcelPropertiesRequestByID", protocol, blocks, Helpers.MSG_RELIABLE); + } } } Index: Parcel.cs =================================================================== --- Parcel.cs (revision 106) +++ Parcel.cs (working copy) @@ -35,33 +35,62 @@ public LLUUID ID; public LLUUID OwnerID; public LLUUID SnapshotID; + public LLUUID AuthBuyerID; public U64 RegionHandle; public string Name; public string SimName; public string Desc; public int SalePrice; public int ActualArea; + public int LocalID; + public int ClaimDate; public LLVector3 GlobalPosition; public LLVector3 SimPosition; public float Dwell; + public byte[] Bitmap; + public bool IsGroupOwned; + public bool FirstLand; + private Simulator Sim; // Using Sim instead of Region since it references both + public Parcel() { GlobalPosition = new LLVector3(); SimPosition = new LLVector3(); + Bitmap = new byte[512]; } + public Parcel(Simulator simulator) + { + Sim = simulator; + GlobalPosition = new LLVector3(); + SimPosition = new LLVector3(); + Bitmap = new byte[512]; + } + public bool Buy(SecondLife client, bool forGroup, LLUUID groupID) { - //SIDENOTE: Teleport should not finish until we update the current region name! + Packet buyPacket = Packets.Parcel.ParcelBuy(client.Protocol,LocalID,forGroup,groupID,true,client.Avatar.ID,client.Network.SessionID); + Sim.SendPacket(buyPacket,true); - // Sanity check to make sure we're in the same sim + return false; + } - // Attempt to buy the parcel + public bool Reclaim(SecondLife client) + { + Packet reclaimPacket = Packets.Parcel.ParcelReclaim(client.Protocol,LocalID,client.Avatar.ID,client.Network.SessionID); + Sim.SendPacket(reclaimPacket,true); - // Check if the purchase was successful (look for money packet?) return false; } + + public bool Deed(SecondLife client, LLUUID groupID) + { + Packet deedPacket = Packets.Parcel.ParcelDeedToGroup(client.Protocol,LocalID,groupID,client.Avatar.ID,client.Network.SessionID); + Sim.SendPacket(deedPacket,true); + + return false; + } } public class ParcelManager @@ -88,6 +117,8 @@ Client.Network.RegisterCallback("DirLandReply", callback); callback = new PacketCallback(ParcelInfoReplyHandler); Client.Network.RegisterCallback("ParcelInfoReply", callback); + callback = new PacketCallback(ParcelPropertiesHandler); + Client.Network.RegisterCallback("ParcelProperties",callback); } public bool RequestParcelInfo(Parcel parcel) @@ -131,6 +162,221 @@ return true; } + public void ParcelPropertiesHandler(Packet packet, Simulator simulator) + { + // Marked == Added to Parcel Class specifically for this Packet + // -> XYZ == Equivilent to property XYZ in Packet. + int RequestResult = 0; + int SequenceID = 0; + bool SnapSelection = false; + int SelfCount = 0; + int OtherCount = 0; + int PublicCount = 0; + int LocalID = 0; // Marked + LLUUID OwnerID = new LLUUID(); // -> OwnerID + bool IsGroupOwned = false; // Marked + uint AuctionID = 0; + bool ReservedNewbie = false; // Marked -> FirstLand + int ClaimDate = 0; // Marked + int ClaimPrice = 0; + int RentPrice = 0; + LLVector3 AABBMin = new LLVector3(); + LLVector3 AABBMax = new LLVector3(); + byte[] Bitmap = new byte[512]; // Marked + int Area = 0; // -> ActualArea + byte Status = 0; + int SimWideMaxObjects = 0; + int SimWideTotalObjects = 0; + int MaxObjects = 0; + int TotalObjects = 0; + int OwnerObjects = 0; + int GroupObjects = 0; + int OtherObjects = 0; + float ParcelObjectBonus = 0.0f; + int OtherCleanTime = 0; + uint ParcelFlags = 0; + int SalePrice = 0; // -> SalePrice + string Name = ""; + string Desc = ""; + string MusicURL = ""; + string MediaURL = ""; + LLUUID MediaID = new LLUUID(); + byte MediaAutoScale = 0; + LLUUID GroupID = new LLUUID(); + int PassPrice = 0; + float PassHours = 0.0f; + byte Category = 0; + LLUUID AuthBuyerID = new LLUUID(); // Marked + LLUUID SnapshotID = new LLUUID(); // -> SnapshotID + LLVector3 UserLocation = new LLVector3(); + LLVector3 UserLookAt = new LLVector3(); + byte LandingType = 0; + + foreach( Block block in packet.Blocks()) + { + foreach(Field field in block.Fields) + { + if(field.Layout.Name == "RequestResult") + RequestResult = (int)field.Data; + else if(field.Layout.Name == "SequenceID") + SequenceID = (int)field.Data; + else if(field.Layout.Name == "SnapSelection") + SnapSelection = (bool)field.Data; + else if(field.Layout.Name == "SelfCount") + SelfCount = (int)field.Data; + else if(field.Layout.Name == "OtherCount") + OtherCount = (int)field.Data; + else if(field.Layout.Name == "PublicCount") + PublicCount = (int)field.Data; + else if(field.Layout.Name == "LocalID") + LocalID = (int)field.Data; + else if(field.Layout.Name == "OwnerID") + OwnerID = (LLUUID)field.Data; + else if(field.Layout.Name == "IsGroupOwned") + IsGroupOwned = (bool)field.Data; + else if(field.Layout.Name == "AuctionID") + AuctionID = (uint)field.Data; + else if(field.Layout.Name == "ReservedNewbie") + ReservedNewbie = (bool)field.Data; + else if(field.Layout.Name == "ClaimDate") + ClaimDate = (int)field.Data; + else if(field.Layout.Name == "ClaimPrice") + ClaimPrice = (int)field.Data; + else if(field.Layout.Name == "RentPrice") + RentPrice = (int)field.Data; + else if(field.Layout.Name == "AABBMin") + AABBMin = (LLVector3)field.Data; + else if(field.Layout.Name == "AABBMax") + AABBMax = (LLVector3)field.Data; + else if(field.Layout.Name == "Bitmap") + Bitmap = (byte[])field.Data; + else if(field.Layout.Name == "Area") + Area = (int)field.Data; + else if(field.Layout.Name == "Status") + Status = (byte)field.Data; + else if(field.Layout.Name == "SimWideMaxObjects") + SimWideMaxObjects = (int)field.Data; + else if(field.Layout.Name == "SimWideTotalObjects") + SimWideTotalObjects = (int)field.Data; + else if(field.Layout.Name == "MaxObjects") + MaxObjects = (int)field.Data; + else if(field.Layout.Name == "TotalObjects") + TotalObjects = (int)field.Data; + else if(field.Layout.Name == "OwnerObjects") + OwnerObjects = (int)field.Data; + else if(field.Layout.Name == "GroupObjects") + GroupObjects = (int)field.Data; + else if(field.Layout.Name == "OtherObjects") + OtherObjects = (int)field.Data; + else if(field.Layout.Name == "ParcelObjectBonus") + ParcelObjectBonus = (float)field.Data; + else if(field.Layout.Name == "OtherCleanTime") + OtherCleanTime = (int)field.Data; + else if(field.Layout.Name == "ParcelFlags") + ParcelFlags = (uint)field.Data; + else if(field.Layout.Name == "SalePrice") + SalePrice = (int)field.Data; + else if(field.Layout.Name == "Name") + Name = System.Text.Encoding.UTF8.GetString((byte[])field.Data).Replace("\0", ""); + else if(field.Layout.Name == "Desc") + Desc = System.Text.Encoding.UTF8.GetString((byte[])field.Data).Replace("\0", ""); + else if(field.Layout.Name == "MusicURL") + MusicURL = System.Text.Encoding.UTF8.GetString((byte[])field.Data).Replace("\0", ""); + else if(field.Layout.Name == "MediaURL") + MediaURL = System.Text.Encoding.UTF8.GetString((byte[])field.Data).Replace("\0", ""); + else if(field.Layout.Name == "MediaID") + MediaID = (LLUUID)field.Data; + else if(field.Layout.Name == "MediaAutoScale") + MediaAutoScale = (byte)field.Data; + else if(field.Layout.Name == "GroupID") + GroupID = (LLUUID)field.Data; + else if(field.Layout.Name == "PassPrice") + PassPrice = (int)field.Data; + else if(field.Layout.Name == "PassHours") + PassHours = (float)field.Data; + else if(field.Layout.Name == "Category") + Category = (byte)field.Data; + else if(field.Layout.Name == "AuthBuyerID") + AuthBuyerID = (LLUUID)field.Data; + else if(field.Layout.Name == "SnapshotID") + SnapshotID = (LLUUID)field.Data; + else if(field.Layout.Name == "UserLocation") + UserLocation = (LLVector3)field.Data; + else if(field.Layout.Name == "UserLookAt") + UserLookAt = (LLVector3)field.Data; + else if(field.Layout.Name == "LandingType") + LandingType = (byte)field.Data; +// else +// Helpers.Log("Unknown field type '" + field.Layout.Name + "' in ParcelProperties",Helpers.LogLevel.Warning); + + } + } + + /* Mark this area as downloaded */ + int x, y, index, subindex; + byte val; + + for(x = 0; x < 64; x++) + { + for(y = 0; y < 64; y++) + { + if(simulator.Region.ParcelMarked[y,x] == 0) + { + index = ((x * 64) + y); + subindex = index % 8; + index /= 8; + + val = Bitmap[index]; + + simulator.Region.ParcelMarked[y,x] = ((val >> subindex) & 1) == 1 ? LocalID : 0; + } + } + } + + /* Fire off the next request, if we are downloading the whole sim */ + if(simulator.Region.ParcelDownloading == true) + { + for(x = 0; x < 64; x++) + { + for(y = 0; y < 64; y++) + { + if(simulator.Region.ParcelMarked[x,y] == 0) + { + Client.Network.SendPacket(libsecondlife.Packets.Parcel.ParcelPropertiesRequest(Client.Protocol,Client.Avatar.ID, -10000 - (x*64) - y, + (x*4.0f),(y*4.0f),(x*4.0f) + 4.0f,(y*4.0f) + 4.0f, false)); + + goto exit; + } + } + } + exit:; + } + + /* Save this parcels data */ + // TODO: Lots of values are not being stored, Parcel needs to be expanded to take all the data. + simulator.Region.ParcelsMutex.WaitOne(); + + if(!simulator.Region.Parcels.ContainsKey(LocalID)) + { + simulator.Region.Parcels[LocalID] = new Parcel(simulator); + } + ((Parcel)simulator.Region.Parcels[LocalID]).Name = Name; + ((Parcel)simulator.Region.Parcels[LocalID]).Desc = Desc; + + ((Parcel)simulator.Region.Parcels[LocalID]).LocalID = LocalID; + ((Parcel)simulator.Region.Parcels[LocalID]).IsGroupOwned = IsGroupOwned; + ((Parcel)simulator.Region.Parcels[LocalID]).Bitmap = Bitmap; + ((Parcel)simulator.Region.Parcels[LocalID]).SalePrice = SalePrice; + ((Parcel)simulator.Region.Parcels[LocalID]).OwnerID = OwnerID; + ((Parcel)simulator.Region.Parcels[LocalID]).FirstLand = ReservedNewbie; + ((Parcel)simulator.Region.Parcels[LocalID]).ActualArea = Area; + ((Parcel)simulator.Region.Parcels[LocalID]).SalePrice = SalePrice; + ((Parcel)simulator.Region.Parcels[LocalID]).AuthBuyerID = AuthBuyerID; + ((Parcel)simulator.Region.Parcels[LocalID]).SnapshotID = SnapshotID; + + simulator.Region.ParcelsMutex.ReleaseMutex(); + } + private void ParcelInfoReplyHandler(Packet packet, Simulator simulator) { string simName = ""; Index: Region.cs =================================================================== --- Region.cs (revision 106) +++ Region.cs (working copy) @@ -39,6 +39,12 @@ public byte[] ParcelOverlay; public int ParcelOverlaysReceived; + public int[,] ParcelMarked; // 64x64 Array of parcels which have been successfully downloaded. (and their LocalID's, 0 = Null) + public bool ParcelDownloading; // Flag to indicate whether we are downloading a sim's parcels. + + public System.Collections.Hashtable Parcels; + public System.Threading.Mutex ParcelsMutex; + public float TerrainHeightRange00; public float TerrainHeightRange01; public float TerrainHeightRange10; @@ -73,6 +79,12 @@ ParcelOverlay = new byte[4096]; ParcelOverlaysReceived = 0; + ParcelMarked = new int[64,64]; + ParcelDownloading = false; + + Parcels = new System.Collections.Hashtable(); + ParcelsMutex = new System.Threading.Mutex(false,"ParcelsMutex"); + SimOwner = new LLUUID(); TerrainBase0 = new LLUUID(); @@ -95,6 +107,8 @@ Handle = handle; Name = name; ParcelOverlay = new byte[4096]; + ParcelMarked = new int[64,64]; + ParcelDownloading = false; TerrainHeightRange00 = heightList[0]; TerrainHeightRange01 = heightList[1]; @@ -132,63 +146,16 @@ Client.Network.SendPacket(objectAdd); } - private static void ParcelOverlayToParcels_bitfill(int x, int y, int[,] Parcels, int index) + public void FillParcels() { - if(x < 0 || x >= 128) return; - if(y < 0 || y >= 128) return; + // Begins filling parcels + ParcelDownloading = true; - if(Parcels[x,y] == 0) - { - Parcels[x,y] = index; - ParcelOverlayToParcels_bitfill(x-1,y,Parcels,index); - ParcelOverlayToParcels_bitfill(x+1,y,Parcels,index); - ParcelOverlayToParcels_bitfill(x-1,y-1,Parcels,index); - ParcelOverlayToParcels_bitfill(x-1,y+1,Parcels,index); - } + // TODO: Replace Client.Network with Region.Simulator, or similar? + Client.Network.SendPacket(libsecondlife.Packets.Parcel.ParcelPropertiesRequest(Client.Protocol,Client.Avatar.ID, -10000, + 0.0f,0.0f,4.0f,4.0f, false)); } - public static int[,] ParcelOverlayToParcels(Region region) - { - byte[] Overlay = region.ParcelOverlay; - int[,] ParcelsHigh = new int[128, 128]; - int[,] Parcels = new int[64, 64]; - - int x, y; - int x2, y2; - int index; - - for(x = 0; x < 64; x++) - for(y = 0; y < 64; y++) - { - x2 = x * 2; - y2 = y * 2; - ParcelsHigh[x2,y2] = 0; - ParcelsHigh[x2 + 1,y2] = (Overlay[x * 64 + y] & 64) == 64 ? -1 : 0; - ParcelsHigh[x2,y2 + 1] = (Overlay[x * 64 + y] & 128) == 128 ? -1 : 0; - ParcelsHigh[x2+1,y2+1] = (ParcelsHigh[x2+1,y2] == -1 || ParcelsHigh[x2,y2 + 1] == -1) ? -1 : 0; - } - - index = 0; - for(x = 0; x < 64; x++) - for(y = 0; y < 64; y++) - { - x2 = x * 2; - y2 = y * 2; - if(ParcelsHigh[x2,y2] == 0) - { - ParcelOverlayToParcels_bitfill(x2,y2,ParcelsHigh,index++); - } - } - for(x = 0; x < 64; x++) - for(y = 0; y < 64; y++) - { - x2 = x * 2; - y2 = y * 2; - Parcels[x,y] = ParcelsHigh[x2,y2]; - } - return Parcels; - } - public override int GetHashCode() { return ID.GetHashCode();
using System; using System.Collections; using libsecondlife; namespace ParcelDownloader { /// <summary> /// Summary description for ParcelDownload. /// </summary> class ParcelDownload { static SecondLife client; /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { if (args.Length < 3) { Console.WriteLine("Usage: ParcelDownloader [loginfirstname] [loginlastname] [password]"); return; } try { client = new SecondLife("keywords.txt", "protocol.txt"); } catch (Exception e) { // Error initializing the client, probably missing file(s) Console.WriteLine(e.ToString()); return; } Hashtable loginParams = NetworkManager.DefaultLoginValues(args[0], args[1], args[2], "00:00:00:00:00:00", "last", 1, 50, 50, 50, "Win", "0", "ParcelDownload", "Adam \"Zaius\" Frisby <[EMAIL PROTECTED]>"); if (!client.Network.Login(loginParams)) { // Login failed Console.WriteLine("ERROR: " + client.Network.LoginError); return; } // The magic happens in these three lines client.CurrentRegion.FillParcels(); // Tell libsl to download parcels System.Threading.Thread.Sleep(10000); // Give it some time to do it client.Tick(); // Let things happen // Dump some info about our parcels foreach(int pkey in client.CurrentRegion.Parcels.Keys) { Parcel parcel = (Parcel)client.CurrentRegion.Parcels[pkey]; parcel.Buy(client,false,new LLUUID()); Console.WriteLine("<Parcel>"); Console.WriteLine("\tName: " + parcel.Name); Console.WriteLine("\tSize: " + parcel.ActualArea); Console.WriteLine("\tDesc: " + parcel.Desc); } client.Network.Logout(); return; } } }
_______________________________________________ libsecondlife-dev mailing list libsecondlife-dev@gna.org https://mail.gna.org/listinfo/libsecondlife-dev