//
// 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; }
}
}
}