From 3b6bfbc1ce480a0d832dd3040ef64a09e975ff82 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Fri, 13 Apr 2007 20:02:21 +0000 Subject: [PATCH] * Caps initial connection is now asynchronous. This will fix the last remaining occasional lockup on program exit * Fixed a lockup on exit and NullReferenceException when using ParcelInfoCommand in TestClient * Basic (but incomplete) support for decoding foliage in CompressedUpdateHandler * More exception handling in Login.cs * Housekeeping in MainAvatar.cs * Increased teleport and caps timeouts * Added an LLVector3 * LLVector3 operator overload git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1115 52acb1d6-8a22-11de-b505-999d5b087335 --- libsecondlife/Caps.cs | 71 +++++++--- libsecondlife/Login.cs | 46 +++++-- libsecondlife/MainAvatar.cs | 125 ++++++++---------- libsecondlife/ObjectManager.cs | 50 +++++-- libsecondlife/Settings.cs | 7 +- libsecondlife/Types.cs | 11 ++ .../TestClient/Commands/ParcelInfoCommand.cs | 11 +- 7 files changed, 198 insertions(+), 123 deletions(-) diff --git a/libsecondlife/Caps.cs b/libsecondlife/Caps.cs index a84dc8ac..7dd97134 100644 --- a/libsecondlife/Caps.cs +++ b/libsecondlife/Caps.cs @@ -69,6 +69,7 @@ namespace libsecondlife private bool Dead = false; private Thread CapsThread; private string EventQueueCap = String.Empty; + private HttpWebRequest CapsRequest = null; private HttpWebRequest EventQueueRequest = null; /// @@ -116,7 +117,6 @@ namespace libsecondlife private void Run() { - byte[] buffer = null; ArrayList req = new ArrayList(); req.Add("MapLayer"); req.Add("MapLayerGod"); @@ -142,20 +142,15 @@ namespace libsecondlife try { - HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(Seedcaps); - request.KeepAlive = false; - request.Timeout = Client.Settings.CAPS_TIMEOUT; - request.Method = "POST"; - request.ContentLength = data.Length; + CapsRequest = (HttpWebRequest)HttpWebRequest.Create(Seedcaps); + CapsRequest.KeepAlive = false; + CapsRequest.Timeout = Client.Settings.CAPS_TIMEOUT; + CapsRequest.Method = "POST"; + CapsRequest.ContentLength = data.Length; - Stream reqStream = request.GetRequestStream(); - reqStream.Write(data, 0, data.Length); - reqStream.Close(); + CapsRequest.BeginGetRequestStream(new AsyncCallback(CapsRequestCallback), data); - WebResponse response = request.GetResponse(); - BinaryReader reader = new BinaryReader(response.GetResponseStream()); - buffer = reader.ReadBytes((int)response.ContentLength); - response.Close(); + Client.DebugLog("Requesting initial CAPS request stream"); } catch (WebException e) { @@ -175,23 +170,57 @@ namespace libsecondlife { Client.Log("CAPS initialization error for " + Simulator.ToString() + ": " + e.Message + ", retrying", Helpers.LogLevel.Warning); + // FIXME: No goto MakeRequest; } } + } - Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer); + private void CapsRequestCallback(IAsyncResult result) + { + byte[] buffer = null; - foreach (string cap in resp.Keys) + try { - //Client.DebugLog(String.Format("Got cap {0}: {1}", cap, (string)resp[cap])); - Capabilities[cap] = (string)resp[cap]; + Client.DebugLog("Writing to initial CAPS request stream"); + + byte[] data = (byte[])result.AsyncState; + Stream reqStream = CapsRequest.EndGetRequestStream(result); + + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + + Client.DebugLog("Requesting initial CAPS response stream"); + + WebResponse response = CapsRequest.GetResponse(); + BinaryReader reader = new BinaryReader(response.GetResponseStream()); + buffer = reader.ReadBytes((int)response.ContentLength); + response.Close(); + } + catch (WebException e) + { + Client.Log("CAPS error initializing the connection, retrying. " + e.Message, + Helpers.LogLevel.Warning); + Run(); + return; } - if (Capabilities.ContainsKey("EventQueueGet")) + if (buffer != null) { - EventQueueCap = Capabilities["EventQueueGet"]; - Client.DebugLog("Running event queue for " + Simulator.ToString()); - EventQueueHandler(null); + Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer); + + foreach (string cap in resp.Keys) + { + Client.DebugLog(String.Format("Got cap {0}: {1}", cap, (string)resp[cap])); + Capabilities[cap] = (string)resp[cap]; + } + + if (Capabilities.ContainsKey("EventQueueGet")) + { + EventQueueCap = Capabilities["EventQueueGet"]; + Client.DebugLog("Running event queue for " + Simulator.ToString()); + EventQueueHandler(null); + } } } diff --git a/libsecondlife/Login.cs b/libsecondlife/Login.cs index b87e6c51..d50e1ba6 100644 --- a/libsecondlife/Login.cs +++ b/libsecondlife/Login.cs @@ -554,7 +554,9 @@ namespace libsecondlife // FIXME: break; default: - // FIXME: + Client.Log("Unhandled element in login reply (gestures)", + Helpers.LogLevel.Error); + reader.Skip(); break; } @@ -578,7 +580,7 @@ namespace libsecondlife int categoryID; string categoryName; - while (ReadCategoryMember(reader, out categoryID, out categoryName)) + while (ReadCategoryMember(Client, reader, out categoryID, out categoryName)) { // FIXME: } @@ -597,7 +599,7 @@ namespace libsecondlife int categoryID; string categoryName; - while (ReadCategoryMember(reader, out categoryID, out categoryName)) + while (ReadCategoryMember(Client, reader, out categoryID, out categoryName)) { // FIXME: } @@ -626,7 +628,8 @@ namespace libsecondlife int buddyRightsGiven, buddyRightsHas; LLUUID buddyID; - while (ReadBuddyMember(reader, out buddyRightsGiven, out buddyRightsHas, out buddyID)) + while (ReadBuddyMember(Client, reader, out buddyRightsGiven, out buddyRightsHas, + out buddyID)) { // FIXME: } @@ -650,7 +653,9 @@ namespace libsecondlife // FIXME: break; default: - // FIXME: + Client.Log("Unhandled element in login reply (ui-config)", + Helpers.LogLevel.Error); + reader.Skip(); break; } } @@ -685,7 +690,9 @@ namespace libsecondlife // FIXME: break; default: - // FIXME: + Client.Log("Unhandled element in login reply (login-flags)", + Helpers.LogLevel.Error); + reader.Skip(); break; } } @@ -717,7 +724,9 @@ namespace libsecondlife // FIXME: break; default: - // FIXME: + Client.Log("Unhandled element in login reply (global-textures)", + Helpers.LogLevel.Error); + reader.Skip(); break; } } @@ -729,7 +738,8 @@ namespace libsecondlife reader.ReadEndElement(); break; default: - // FIXME: + Client.Log("Unhandled element in login reply", Helpers.LogLevel.Error); + reader.Skip(); break; } @@ -783,6 +793,9 @@ namespace libsecondlife Client.Network.LoginMessage = value; } + + reader.Close(); + response.Close(); } catch (WebException e) { @@ -792,7 +805,7 @@ namespace libsecondlife catch (XmlException e) { LoginErrorKey = "libsl"; - LoginMessage = "Error parsing reply XML: " + e.Message; + LoginMessage = "Error parsing reply XML: " + e.Message + Environment.NewLine + e.StackTrace; } LoginEvent.Set(); @@ -898,7 +911,8 @@ namespace libsecondlife return ret; } - private static bool ReadCategoryMember(XmlReader reader, out int categoryID, out string categoryName) + private static bool ReadCategoryMember(SecondLife client, XmlReader reader, out int categoryID, + out string categoryName) { categoryID = 0; categoryName = String.Empty; @@ -928,7 +942,9 @@ namespace libsecondlife categoryName = ReadStringValue(reader); break; default: - // FIXME: + client.Log("Unhandled element in login reply (CategoryMember)", + Helpers.LogLevel.Error); + reader.Skip(); break; } @@ -945,8 +961,8 @@ namespace libsecondlife return ret; } - private static bool ReadBuddyMember(XmlReader reader, out int buddyRightsGiven, out int buddyRightsHas, - out LLUUID buddyID) + private static bool ReadBuddyMember(SecondLife client, XmlReader reader, out int buddyRightsGiven, + out int buddyRightsHas, out LLUUID buddyID) { buddyRightsGiven = 0; buddyRightsHas = 0; @@ -981,7 +997,9 @@ namespace libsecondlife buddyRightsHas = ReadIntegerValue(reader); break; default: - // FIXME: + client.Log("Unhandled element in login reply (BuddyMember)", + Helpers.LogLevel.Error); + reader.Skip(); break; } diff --git a/libsecondlife/MainAvatar.cs b/libsecondlife/MainAvatar.cs index 383a6ff9..3c1d86af 100644 --- a/libsecondlife/MainAvatar.cs +++ b/libsecondlife/MainAvatar.cs @@ -498,7 +498,6 @@ namespace libsecondlife #endregion - #region Callbacks & Events /// /// Triggered on incoming chat messages @@ -629,7 +628,6 @@ namespace libsecondlife #endregion - #region Public Members /// Your (client) avatar UUID @@ -696,7 +694,6 @@ namespace libsecondlife #endregion Public Members - internal uint sittingOn = 0; internal string teleportMessage = String.Empty; internal DateTime lastInterpolation; @@ -709,7 +706,6 @@ namespace libsecondlife private int balance = 0; private LLUUID activeGroup = LLUUID.Zero; - #region AgentUpdate Constants private const int CONTROL_AT_POS_INDEX = 0; @@ -792,8 +788,6 @@ namespace libsecondlife Client.Network.RegisterCallback(PacketType.MoneySummaryReply, callback); Client.Network.RegisterCallback(PacketType.AdjustBalance, callback); - Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, new NetworkManager.PacketCallback(MoneyBalanceReplyHandler)); - // Group callbacks Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupHandler)); Client.Network.RegisterCallback(PacketType.LeaveGroupReply, new NetworkManager.PacketCallback(LeaveGroupHandler)); @@ -813,7 +807,7 @@ namespace libsecondlife /// Text message being sent public void InstantMessage(LLUUID target, string message) { - InstantMessage(FirstName + " " + LastName, target, message, LLUUID.Random(), + InstantMessage(Client.ToString(), target, message, LLUUID.Random(), InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]); } @@ -826,7 +820,7 @@ namespace libsecondlife /// IM session ID (to differentiate between IM windows) public void InstantMessage(LLUUID target, string message, LLUUID imSessionID) { - InstantMessage(FirstName + " " + LastName, target, message, imSessionID, + InstantMessage(Client.ToString(), target, message, imSessionID, InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]); } @@ -910,7 +904,7 @@ namespace libsecondlife /// Text Message being sent. public void InstantMessageGroup(LLUUID groupUUID, string message) { - InstantMessageGroup(FirstName + " " + LastName, groupUUID, message); + InstantMessageGroup(Client.ToString(), groupUUID, message); } /// @@ -1085,7 +1079,6 @@ namespace libsecondlife { // Basic profile properties AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket(); - apup.AgentData.AgentID = this.ID; apup.AgentData.SessionID = Client.Network.SessionID; apup.PropertiesData.AboutText = Helpers.StringToField(this.ProfileProperties.AboutText); @@ -1096,9 +1089,10 @@ namespace libsecondlife apup.PropertiesData.MaturePublish = this.ProfileProperties.MaturePublish; apup.PropertiesData.ProfileURL = Helpers.StringToField(this.ProfileProperties.ProfileURL); + Client.Network.SendPacket(apup); + // Interests AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket(); - aiup.AgentData.AgentID = this.ID; aiup.AgentData.SessionID = Client.Network.SessionID; aiup.PropertiesData.LanguagesText = Helpers.StringToField(this.ProfileInterests.LanguagesText); @@ -1107,8 +1101,6 @@ namespace libsecondlife aiup.PropertiesData.WantToMask = this.ProfileInterests.WantToMask; aiup.PropertiesData.WantToText = Helpers.StringToField(this.ProfileInterests.WantToText); - //Send packets - Client.Network.SendPacket(apup); Client.Network.SendPacket(aiup); } @@ -1448,7 +1440,7 @@ namespace libsecondlife teleport.Info.Position = position; teleport.Info.RegionHandle = regionHandle; - Client.Log("Teleporting to region " + regionHandle.ToString(), Helpers.LogLevel.Info); + Client.Log("Requesting teleport to region handle " + regionHandle.ToString(), Helpers.LogLevel.Info); Client.Network.SendPacket(teleport); } @@ -1461,7 +1453,7 @@ namespace libsecondlife /// Accept the teleport request or deny it public void TeleportLureRespond(LLUUID requesterID, bool accept) { - InstantMessage(FirstName + " " + LastName, requesterID, String.Empty, LLUUID.Random(), + InstantMessage(Client.ToString(), requesterID, String.Empty, LLUUID.Random(), accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport, InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]); @@ -1593,6 +1585,36 @@ namespace libsecondlife Client.Network.SendPacket(move, simulator); } + /// + /// Set this avatar's tier contribution + /// + /// Group to change tier in + /// amount of tier to donate + public void SetGroupContribution(LLUUID group, int contribution) + { + libsecondlife.Packets.SetGroupContributionPacket sgp = new SetGroupContributionPacket(); + sgp.AgentData.AgentID = Client.Network.AgentID; + sgp.AgentData.SessionID = Client.Network.SessionID; + sgp.Data.GroupID = group; + sgp.Data.Contribution = contribution; + Client.Network.SendPacket(sgp); + } + + /// + /// Change the role that determines your active title + /// + /// Group to use + /// Role to change to + public void ChangeTitle(LLUUID group, LLUUID role) + { + libsecondlife.Packets.GroupTitleUpdatePacket gtu = new GroupTitleUpdatePacket(); + gtu.AgentData.AgentID = Client.Network.AgentID; + gtu.AgentData.SessionID = Client.Network.SessionID; + gtu.AgentData.TitleRoleID = role; + gtu.AgentData.GroupID = group; + Client.Network.SendPacket(gtu); + } + /// /// Sends camera and action updates to the server including the /// position and orientation of our camera, and a ControlFlags field @@ -1649,6 +1671,8 @@ namespace libsecondlife Client.Network.SendPacket(yes, simulator); } + #region Packet Handlers + /// /// Take an incoming ImprovedInstantMessage packet, auto-parse, and if /// OnInstantMessage is defined call that with the appropriate arguments @@ -1832,34 +1856,26 @@ namespace libsecondlife { balance += ((AdjustBalancePacket)packet).AgentData.Delta; } - - if (OnBalanceUpdated != null) + else if (packet.Type == PacketType.MoneyBalanceReply) { - OnBalanceUpdated(balance); - } - } + MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet; + balance = mbrp.MoneyData.MoneyBalance; - /// - /// Update Client Avatar's L$ balance from incoming packet, and - /// trigger a complete callback with all available fields. - /// - /// - /// - private void MoneyBalanceReplyHandler(Packet packet, Simulator simulator) - { - MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet; - balance = mbrp.MoneyData.MoneyBalance; - - if (OnMoneyBalanceReplyReceived != null) - { - OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID, mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance, mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted, Helpers.FieldToUTF8String(mbrp.MoneyData.Description)); + if (OnMoneyBalanceReplyReceived != null) + { + try { OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID, + mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance, + mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted, + Helpers.FieldToUTF8String(mbrp.MoneyData.Description)); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } } if (OnBalanceUpdated != null) { - OnBalanceUpdated(balance); + try { OnBalanceUpdated(balance); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } - } private void EventQueueHandler(string message, Hashtable body, Caps caps) @@ -1921,7 +1937,8 @@ namespace libsecondlife flags = (TeleportFlags)progress.Info.TeleportFlags; TeleportStat = TeleportStatus.Progress; - Client.DebugLog("TeleportProgress received from " + simulator.ToString() + ", Flags: " + flags.ToString()); + Client.DebugLog("TeleportProgress received from " + simulator.ToString() + "Message: " + + teleportMessage + ", Flags: " + flags.ToString()); } else if (packet.Type == PacketType.TeleportFailed) { @@ -1974,7 +1991,7 @@ namespace libsecondlife { //TeleportCancelPacket cancel = (TeleportCancelPacket)packet; - teleportMessage = "Cancelled."; + teleportMessage = "Cancelled"; TeleportStat = TeleportStatus.Cancelled; finished = true; @@ -2004,37 +2021,7 @@ namespace libsecondlife if (finished) TeleportEvent.Set(); } - - /// - /// Set this avatar's tier contribution - /// - /// Group to change tier in - /// amount of tier to donate - public void SetGroupContribution(LLUUID group, int contribution) - { - libsecondlife.Packets.SetGroupContributionPacket sgp = new SetGroupContributionPacket(); - sgp.AgentData.AgentID = Client.Network.AgentID; - sgp.AgentData.SessionID = Client.Network.SessionID; - sgp.Data.GroupID = group; - sgp.Data.Contribution = contribution; - Client.Network.SendPacket(sgp); - } - - /// - /// Change the role that determines your active title - /// - /// Group to use - /// Role to change to - public void ChangeTitle(LLUUID group, LLUUID role) - { - libsecondlife.Packets.GroupTitleUpdatePacket gtu = new GroupTitleUpdatePacket(); - gtu.AgentData.AgentID = Client.Network.AgentID; - gtu.AgentData.SessionID = Client.Network.SessionID; - gtu.AgentData.TitleRoleID = role; - gtu.AgentData.GroupID = group; - Client.Network.SendPacket(gtu); - } - } + #endregion Packet Handlers } diff --git a/libsecondlife/ObjectManager.cs b/libsecondlife/ObjectManager.cs index a33653f4..1930bbec 100644 --- a/libsecondlife/ObjectManager.cs +++ b/libsecondlife/ObjectManager.cs @@ -147,7 +147,7 @@ namespace libsecondlife /// /// /// - public enum PCode + public enum PCode : byte { /// Prim = 9, @@ -166,7 +166,7 @@ namespace libsecondlife /// /// /// - public enum AttachmentPoint + public enum AttachmentPoint : byte { /// Default = 0, @@ -253,7 +253,7 @@ namespace libsecondlife /// which options are present for each object /// [Flags] - public enum CompressedFlags + public enum CompressedFlags : uint { /// Hasn't been spotted in the wild yet ScratchPad = 0x01, @@ -280,7 +280,7 @@ namespace libsecondlife /// /// /// - public enum Tree + public enum Tree : byte { /// Pine1 = 0, @@ -329,7 +329,7 @@ namespace libsecondlife /// /// /// - public enum Grass + public enum Grass : byte { /// Grass0 = 0, @@ -1348,7 +1348,7 @@ namespace libsecondlife prim.ParticleSys = new Primitive.ParticleSystem(block.PSBlock, 0); prim.SetExtraParamsFromBytes(block.ExtraParams, 0); - // Unknown + // PCode-specific data prim.GenericData = block.Data; // Packed parameters @@ -1635,10 +1635,8 @@ namespace libsecondlife uint LocalID = (uint)(block.Data[i++] + (block.Data[i++] << 8) + (block.Data[i++] << 16) + (block.Data[i++] << 24)); - prim = GetPrimitive(simulator, LocalID, FullID); - prim.LocalID = LocalID; prim.ID = FullID; prim.Flags = (LLObject.ObjectFlags)block.UpdateFlags; @@ -1691,17 +1689,16 @@ namespace libsecondlife // Tree data if ((flags & CompressedFlags.Tree) != 0) { - // FIXME: Decode the tree data in to an enum and do something with it - byte treeData = block.Data[i++]; - Client.DebugLog("Compressed object with Tree flag set, TreeData: " + treeData); + prim.GenericData = new byte[1]; + prim.GenericData[0] = block.Data[i++]; } // Scratch pad else if ((flags & CompressedFlags.ScratchPad) != 0) { int size = block.Data[i++]; + prim.GenericData = new byte[size]; + Array.Copy(block.Data, i, prim.GenericData, 0, size); i += size; - Client.Log("Compressed object with ScratchPad flag set, Size: " + size, - Helpers.LogLevel.Warning); } // Floating text @@ -1856,7 +1853,32 @@ namespace libsecondlife case PCode.Grass: case PCode.Tree: case PCode.NewTree: - // FIXME: Implement these!!! + #region Foliage Decoding + // State + prim.data.State = (uint)block.Data[i++]; + // CRC + i += 4; + // Material + prim.data.Material = (uint)block.Data[i++]; + // Click action + prim.ClickAction = (ClickAction)block.Data[i++]; + // Scale + prim.Scale = new LLVector3(block.Data, i); + i += 12; + // Position + prim.Position = new LLVector3(block.Data, i); + i += 12; + // Rotation + prim.Rotation = new LLQuaternion(block.Data, i, true); + i += 12; + #endregion Foliage Decoding + + // FIXME: We are leaving a lot of data left undecoded here, including the + // tree species. Need to understand what is going on with these packets + // and fix it soon! + + FireOnNewFoliage(simulator, prim, update.RegionData.RegionHandle, update.RegionData.TimeDilation); + break; default: Client.DebugLog("Got an ObjectUpdateCompressed for PCode " + pcode.ToString() + diff --git a/libsecondlife/Settings.cs b/libsecondlife/Settings.cs index 36416cc2..dcf7f277 100644 --- a/libsecondlife/Settings.cs +++ b/libsecondlife/Settings.cs @@ -68,16 +68,15 @@ namespace libsecondlife /// Number of milliseconds before a teleport attempt will time /// out - public int TELEPORT_TIMEOUT = 25 * 1000; + public int TELEPORT_TIMEOUT = 40 * 1000; /// Number of milliseconds before NetworkManager.Logout() will /// time out public int LOGOUT_TIMEOUT = 5 * 1000; /// Number of milliseconds before a CAPS call will time out /// and try again /// Setting this too low will cause web requests to repeatedly - /// time out and retry. Too high of a setting may cause the library to - /// block for a long time during network shutdown - public int CAPS_TIMEOUT = 20 * 1000; + /// time out and retry + public int CAPS_TIMEOUT = 60 * 1000; /// Number of milliseconds for xml-rpc to timeout public int LOGIN_TIMEOUT = 60 * 1000; /// Milliseconds before a packet is assumed lost and resent diff --git a/libsecondlife/Types.cs b/libsecondlife/Types.cs index 0a6796ed..95ef9991 100644 --- a/libsecondlife/Types.cs +++ b/libsecondlife/Types.cs @@ -531,6 +531,17 @@ namespace libsecondlife return new LLVector3(vec.X * val, vec.Y * val, vec.Z * val); } + /// + /// + /// + /// + /// + /// + public static LLVector3 operator *(LLVector3 lhs, LLVector3 rhs) + { + return new LLVector3(lhs.X * rhs.X, lhs.Y * rhs.Y, lhs.Z * rhs.Z); + } + public static LLVector3 operator *(LLVector3 vec, LLQuaternion quat) { LLQuaternion vq = new LLQuaternion(vec.X, vec.Y, vec.Z, 0); diff --git a/libsecondlife/examples/TestClient/Commands/ParcelInfoCommand.cs b/libsecondlife/examples/TestClient/Commands/ParcelInfoCommand.cs index 1216ece6..3492a983 100644 --- a/libsecondlife/examples/TestClient/Commands/ParcelInfoCommand.cs +++ b/libsecondlife/examples/TestClient/Commands/ParcelInfoCommand.cs @@ -19,6 +19,7 @@ namespace libsecondlife.TestClient Parcels = new ParcelDownloader(testClient); Parcels.OnParcelsDownloaded += new ParcelDownloader.ParcelsDownloadedCallback(Parcels_OnParcelsDownloaded); + testClient.Network.OnDisconnected += new NetworkManager.DisconnectedCallback(Network_OnDisconnected); } public override string Execute(string[] args, LLUUID fromAgentID) @@ -28,7 +29,10 @@ namespace libsecondlife.TestClient ParcelsDownloaded.Reset(); ParcelsDownloaded.WaitOne(20000, false); - return "Downloaded information for " + ParcelCount + " parcels in " + Client.Network.CurrentSim.Name; + if (Client.Network.CurrentSim != null) + return "Downloaded information for " + ParcelCount + " parcels in " + Client.Network.CurrentSim.Name; + else + return String.Empty; } void Parcels_OnParcelsDownloaded(Simulator simulator, Dictionary Parcels, int[,] map) @@ -43,5 +47,10 @@ namespace libsecondlife.TestClient ParcelsDownloaded.Set(); } + + void Network_OnDisconnected(NetworkManager.DisconnectType reason, string message) + { + ParcelsDownloaded.Set(); + } } }