// // Radegast Metaverse Client // Copyright (c) 2009-2014, Radegast Development Team // 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. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the application "Radegast", 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 HOLDER 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. // // $Id$ // using System; using System.Collections.Generic; using System.ComponentModel; using OpenMetaverse; using Raindrop; using OpenMetaverse.Packets; namespace Raindrop.Netcom { /// /// RadegastNetcom is a class built on top of libsecondlife that provides a way to /// raise events on the proper thread (for GUI apps especially). /// public partial class RaindropNetcom : IDisposable { private RaindropInstance instance; private GridClient client { get { return instance.Client; } } public LoginOptions loginOptions; private bool loggingIn = false; private bool loggedIn = false; private bool teleporting = false; private bool agreeToTos = false; public bool AgreeToTos { get { return agreeToTos; } set { agreeToTos = value; } } public Grid Grid => loginOptions.Grid; // NetcomSync is used for raising certain events on the // GUI/main thread. Useful if you're modifying GUI controls // in the client app when responding to those events. //private Control netcomSync; //we don't use netcomsync or a netcom actually. we just pass all as events to the unitymainthreaddispatcher for now. #region ClientConnected event /// The event subscribers, null of no subscribers private EventHandler m_ClientConnected; ///Raises the ClientConnected Event /// A ClientConnectedEventArgs object containing /// the old and the new client protected virtual void OnClientConnected(EventArgs e) { EventHandler handler = m_ClientConnected; if (handler != null) handler(this, e); } /// Thread sync lock object private readonly object m_ClientConnectedLock = new object(); /// Raise event delegate private delegate void ClientConnectedRaise(EventArgs e); /// Raised when the GridClient object in the main Radegast instance is changed public event EventHandler ClientConnected { add { lock (m_ClientConnectedLock) { m_ClientConnected += value; } } remove { lock (m_ClientConnectedLock) { m_ClientConnected -= value; } } } #endregion ClientConnected event public RaindropNetcom(RaindropInstance instance) { this.instance = instance; loginOptions = new LoginOptions(); instance.ClientChanged += new EventHandler(instance_ClientChanged); RegisterClientEvents(client); } private void RegisterClientEvents(GridClient client) { client.Self.ChatFromSimulator += new EventHandler(Self_ChatFromSimulator); client.Self.IM += new EventHandler(Self_IM); client.Self.MoneyBalance += new EventHandler(Self_MoneyBalance); client.Self.TeleportProgress += new EventHandler(Self_TeleportProgress); client.Self.AlertMessage += new EventHandler(Self_AlertMessage); client.Network.Disconnected += new EventHandler(Network_Disconnected); client.Network.LoginProgress += new EventHandler(Network_LoginProgress); client.Network.LoggedOut += new EventHandler(Network_LoggedOut); } private void UnregisterClientEvents(GridClient client) { client.Self.ChatFromSimulator -= new EventHandler(Self_ChatFromSimulator); client.Self.IM -= new EventHandler(Self_IM); client.Self.MoneyBalance -= new EventHandler(Self_MoneyBalance); client.Self.TeleportProgress -= new EventHandler(Self_TeleportProgress); client.Self.AlertMessage -= new EventHandler(Self_AlertMessage); client.Network.Disconnected -= new EventHandler(Network_Disconnected); client.Network.LoginProgress -= new EventHandler(Network_LoginProgress); client.Network.LoggedOut -= new EventHandler(Network_LoggedOut); } void instance_ClientChanged(object sender, ClientChangedEventArgs e) { UnregisterClientEvents(e.OldClient); RegisterClientEvents(client); } //private bool CanSyncInvoke //{ // get // { // return netcomSync != null && !netcomSync.IsDisposed && netcomSync.IsHandleCreated && netcomSync.InvokeRequired; // } //} public void Dispose() { if (client != null) { UnregisterClientEvents(client); } } void Self_IM(object sender, InstantMessageEventArgs e) { UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Self_IM"); OnInstantMessageReceived(e); }); //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnInstantMessageRaise(OnInstantMessageReceived), new object[] { e }); --queue task to run on the control's(UI's) thread/ //else // OnInstantMessageReceived(e); --run on current thread, since it is main thread. } void Network_LoginProgress(object sender, LoginProgressEventArgs e) { if (e.Status == LoginStatus.Success) { loggedIn = true; client.Self.RequestBalance(); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Network_LoginProgress.Success"); OnClientConnected(EventArgs.Empty); }); //if (CanSyncInvoke) //{ // netcomSync.BeginInvoke(new ClientConnectedRaise(OnClientConnected), new object[] { EventArgs.Empty }); //} //else //{ // OnClientConnected(EventArgs.Empty); //} } if (e.Status == LoginStatus.Failed) { instance.MarkEndExecution(); } LoginProgressEventArgs ea = new LoginProgressEventArgs(e.Status, e.Message, string.Empty); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Network_LoginProgress"); OnClientLoginStatus(e); }); //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnClientLoginRaise(OnClientLoginStatus), new object[] { e }); //else // OnClientLoginStatus(e); } void Network_LoggedOut(object sender, LoggedOutEventArgs e) { loggedIn = false; //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnClientLogoutRaise(OnClientLoggedOut), new object[] { EventArgs.Empty }); //else // OnClientLoggedOut(EventArgs.Empty); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Network_LoggedOut"); OnClientLoggedOut(EventArgs.Empty); }); } void Self_TeleportProgress(object sender, TeleportEventArgs e) { if (e.Status == TeleportStatus.Finished || e.Status == TeleportStatus.Failed) teleporting = false; //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnTeleportStatusRaise(OnTeleportStatusChanged), new object[] { e }); //else // OnTeleportStatusChanged(e); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Self_TeleportProgress"); OnTeleportStatusChanged(e); }); } private void Self_ChatFromSimulator(object sender, ChatEventArgs e) { //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnChatRaise(OnChatReceived), new object[] { e }); //else // OnChatReceived(e); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Self_ChatFromSimulator"); OnChatReceived(e); }); } void Network_Disconnected(object sender, DisconnectedEventArgs e) { loggedIn = false; instance.MarkEndExecution(); //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnClientDisconnectRaise(OnClientDisconnected), new object[] { e }); //else // OnClientDisconnected(e); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Network_Disconnected"); OnClientDisconnected(e); }); } void Self_MoneyBalance(object sender, BalanceEventArgs e) { //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnMoneyBalanceRaise(OnMoneyBalanceUpdated), new object[] { e }); //else // OnMoneyBalanceUpdated(e); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Self_MoneyBalance"); OnMoneyBalanceUpdated(e); }); } void Self_AlertMessage(object sender, AlertMessageEventArgs e) { //if (CanSyncInvoke) // netcomSync.BeginInvoke(new OnAlertMessageRaise(OnAlertMessageReceived), new object[] { e }); //else // OnAlertMessageReceived(e); UnityMainThreadDispatcher.Instance().Enqueue(() => { Logger.DebugLog("netcom callback: Self_AlertMessage"); OnAlertMessageReceived(e); }); } public void Login() { loggingIn = true; // Report crashes only once and not on relogs/reconnects LastExecStatus execStatus = instance.GetLastExecStatus(); if (!instance.AnotherInstanceRunning() && execStatus != LastExecStatus.Normal && (!instance.ReportedCrash)) { instance.ReportedCrash = true; loginOptions.LastExecEvent = execStatus; Logger.Log("Reporting crash of the last application run to the grid login service", Helpers.LogLevel.Warning); } else { loginOptions.LastExecEvent = LastExecStatus.Normal; Logger.Log("Reporting normal shutdown of the last application run to the grid login service", Helpers.LogLevel.Info); } instance.MarkStartExecution(); OverrideEventArgs ea = new OverrideEventArgs(); OnClientLoggingIn(ea); if (ea.Cancel) { loggingIn = false; return; } if (string.IsNullOrEmpty(loginOptions.FirstName) || string.IsNullOrEmpty(loginOptions.LastName) || string.IsNullOrEmpty(loginOptions.Password)) { OnClientLoginStatus( new LoginProgressEventArgs(LoginStatus.Failed, "One or more fields are blank.", string.Empty)); } string startLocation = string.Empty; switch (loginOptions.StartLocation) { case StartLocationType.Home: startLocation = "home"; break; case StartLocationType.Last: startLocation = "last"; break; case StartLocationType.Custom: startLocation = loginOptions.StartLocationCustom.Trim(); StartLocationParser parser = new StartLocationParser(startLocation); startLocation = NetworkManager.StartLocation(parser.Sim, parser.X, parser.Y, parser.Z); break; } string password; if (LoginOptions.IsPasswordMD5(loginOptions.Password)) { password = loginOptions.Password; } else { if (loginOptions.Password.Length > 16) { password = Utils.MD5(loginOptions.Password.Substring(0, 16)); } else { password = Utils.MD5(loginOptions.Password); } } LoginParams loginParams = client.Network.DefaultLoginParams( loginOptions.FirstName, loginOptions.LastName, password, loginOptions.Channel, loginOptions.Version); loginParams.Start = startLocation; loginParams.AgreeToTos = AgreeToTos; loginParams.URI = Grid.LoginURI; loginParams.LastExecEvent = loginOptions.LastExecEvent; client.Network.BeginLogin(loginParams); } public void Logout() { if (!loggedIn) { OnClientLoggedOut(EventArgs.Empty); return; } OverrideEventArgs ea = new OverrideEventArgs(); OnClientLoggingOut(ea); if (ea.Cancel) return; client.Network.Logout(); } public void ChatOut(string chat, ChatType type, int channel) { if (!loggedIn) return; client.Self.Chat(chat, channel, type); OnChatSent(new ChatSentEventArgs(chat, type, channel)); } public void SendInstantMessage(string message, UUID target, UUID session) { if (!loggedIn) return; client.Self.InstantMessage( loginOptions.FullName, target, message, session, InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Online, client.Self.SimPosition, client.Network.CurrentSim.ID, null); OnInstantMessageSent(new InstantMessageSentEventArgs(message, target, session, DateTime.Now)); } public void SendIMStartTyping(UUID target, UUID session) { if (!loggedIn) return; client.Self.InstantMessage( loginOptions.FullName, target, "typing", session, InstantMessageDialog.StartTyping, InstantMessageOnline.Online, client.Self.SimPosition, client.Network.CurrentSim.ID, null); } public void SendIMStopTyping(UUID target, UUID session) { if (!loggedIn) return; client.Self.InstantMessage( loginOptions.FullName, target, "typing", session, InstantMessageDialog.StopTyping, InstantMessageOnline.Online, client.Self.SimPosition, client.Network.CurrentSim.ID, null); } public void Teleport(string sim, Vector3 coordinates) { if (!loggedIn) return; if (teleporting) return; TeleportingEventArgs ea = new TeleportingEventArgs(sim, coordinates); OnTeleporting(ea); if (ea.Cancel) return; teleporting = true; client.Self.Teleport(sim, coordinates); } public bool IsLoggingIn { get { return loggingIn; } } public bool IsLoggedIn { get { return loggedIn; } } public bool IsTeleporting { get { return teleporting; } } public bool IsSecondlife { get { return (this.LoginOptions?.Grid?.Platform == "SecondLife"); } } public LoginOptions LoginOptions { get { return loginOptions; } set { loginOptions = value; } } } }