From f5c312bc3c2567449c7268a54a08a54119f58d53 Mon Sep 17 00:00:00 2001 From: Adam Frisby Date: Wed, 30 Apr 2008 21:16:36 +0000 Subject: * Refactored Environment/Modules directory - modules now reside in their own directory with any associated module-specific classes. * Each module directory is currently inside one of the following category folders: Agent (Anything relating to do with Client<->Server communications.), Avatar (Anything to do with the avatar or presence inworld), Framework (Classes modules can use), Grid (Grid traffic, new OGS2 grid comms), Scripting (Scripting functions, etc), World (The enrivonment/scene, IE Sun/Tree modules.) * This should be moved into a seperate project file. --- .../Avatar/AvatarFactory/AvatarFactoryModule.cs | 338 +++++ .../Environment/Modules/Avatar/Chat/ChatModule.cs | 831 +++++++++++ .../Currency/SampleMoney/SampleMoneyModule.cs | 1491 ++++++++++++++++++++ .../Modules/Avatar/Friends/FriendsModule.cs | 504 +++++++ .../Modules/Avatar/Groups/GroupsModule.cs | 278 ++++ .../Avatar/InstantMessage/InstantMessageModule.cs | 157 +++ .../Modules/Avatar/Inventory/InventoryModule.cs | 216 +++ .../Avatar/Profiles/AvatarProfilesModule.cs | 129 ++ .../Voice/AsterixVoice/AsteriskVoiceModule.cs | 285 ++++ .../Avatar/Voice/SIPVoice/SIPVoiceModule.cs | 197 +++ 10 files changed, 4426 insertions(+) create mode 100644 OpenSim/Region/Environment/Modules/Avatar/AvatarFactory/AvatarFactoryModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Groups/GroupsModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Inventory/InventoryModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Profiles/AvatarProfilesModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs (limited to 'OpenSim/Region/Environment/Modules/Avatar') diff --git a/OpenSim/Region/Environment/Modules/Avatar/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/Environment/Modules/Avatar/AvatarFactory/AvatarFactoryModule.cs new file mode 100644 index 0000000..3614686 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/AvatarFactory/AvatarFactoryModule.cs @@ -0,0 +1,338 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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 libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Data.MySQLMapper; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using OpenSim.Data.Base; + +namespace OpenSim.Region.Environment.Modules +{ + public class AvatarFactoryModule : IAvatarFactory + { + private Scene m_scene = null; + private readonly Dictionary m_avatarsAppearance = new Dictionary(); + + private bool m_enablePersist = false; + private string m_connectionString; + private bool m_configured = false; + private BaseDatabaseConnector m_databaseMapper; + private AppearanceTableMapper m_appearanceMapper; + + private Dictionary m_fetchesInProgress = new Dictionary(); + private object m_syncLock = new object(); + + public bool TryGetAvatarAppearance(LLUUID avatarId, out AvatarAppearance appearance) + { + + //should only let one thread at a time do this part + EventWaitHandle waitHandle = null; + bool fetchInProgress = false; + lock (m_syncLock) + { + appearance = CheckCache(avatarId); + if (appearance != null) + { + return true; + } + + //not in cache so check to see if another thread is already fetching it + if (m_fetchesInProgress.TryGetValue(avatarId, out waitHandle)) + { + fetchInProgress = true; + } + else + { + fetchInProgress = false; + + //no thread already fetching this appearance, so add a wait handle to list + //for any following threads that want the same appearance + waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); + m_fetchesInProgress.Add(avatarId, waitHandle); + } + } + + if (fetchInProgress) + { + waitHandle.WaitOne(); + appearance = CheckCache(avatarId); + if (appearance != null) + { + waitHandle = null; + return true; + } + else + { + waitHandle = null; + return false; + } + } + else + { + Thread.Sleep(5000); + + //this is the first thread to request this appearance + //so let it check the db and if not found then create a default appearance + //and add that to the cache + appearance = CheckDatabase(avatarId); + if (appearance != null) + { + //appearance has now been added to cache so lets pulse any waiting threads + lock (m_syncLock) + { + m_fetchesInProgress.Remove(avatarId); + waitHandle.Set(); + } + // waitHandle.Close(); + waitHandle = null; + return true; + } + + //not found a appearance for the user, so create a new default one + appearance = CreateDefault(avatarId); + if (appearance != null) + { + //update database + if (m_enablePersist) + { + m_appearanceMapper.Add(avatarId.UUID, appearance); + } + + //add appearance to dictionary cache + lock (m_avatarsAppearance) + { + m_avatarsAppearance[avatarId] = appearance; + } + + //appearance has now been added to cache so lets pulse any waiting threads + lock (m_syncLock) + { + m_fetchesInProgress.Remove(avatarId); + waitHandle.Set(); + } + // waitHandle.Close(); + waitHandle = null; + return true; + } + else + { + //something went wrong, so release the wait handle and remove it + //all waiting threads will fail to find cached appearance + //but its better for them to fail than wait for ever + lock (m_syncLock) + { + m_fetchesInProgress.Remove(avatarId); + waitHandle.Set(); + } + //waitHandle.Close(); + waitHandle = null; + return false; + } + } + } + + private AvatarAppearance CreateDefault(LLUUID avatarId) + { + AvatarAppearance appearance = null; + AvatarWearable[] wearables; + byte[] visualParams; + GetDefaultAvatarAppearance(out wearables, out visualParams); + appearance = new AvatarAppearance(avatarId, wearables, visualParams); + + return appearance; + } + + private AvatarAppearance CheckDatabase(LLUUID avatarId) + { + AvatarAppearance appearance = null; + if (m_enablePersist) + { + if (m_appearanceMapper.TryGetValue(avatarId.UUID, out appearance)) + { + appearance.VisualParams = GetDefaultVisualParams(); + appearance.TextureEntry = AvatarAppearance.GetDefaultTextureEntry(); + lock (m_avatarsAppearance) + { + m_avatarsAppearance[avatarId] = appearance; + } + } + } + return appearance; + } + + private AvatarAppearance CheckCache(LLUUID avatarId) + { + AvatarAppearance appearance = null; + lock (m_avatarsAppearance) + { + if (m_avatarsAppearance.ContainsKey(avatarId)) + { + appearance = m_avatarsAppearance[avatarId]; + } + } + return appearance; + } + + public void Initialise(Scene scene, IConfigSource source) + { + scene.RegisterModuleInterface(this); + scene.EventManager.OnNewClient += NewClient; + + if (m_scene == null) + { + m_scene = scene; + } + + if (!m_configured) + { + m_configured = true; + try + { + m_enablePersist = source.Configs["Startup"].GetBoolean("appearance_persist", false); + m_connectionString = source.Configs["Startup"].GetString("appearance_connection_string", ""); + } + catch (Exception) + { + } + if (m_enablePersist) + { + m_databaseMapper = new MySQLDatabaseMapper(m_connectionString); + m_appearanceMapper = new AppearanceTableMapper(m_databaseMapper, "AvatarAppearance"); + } + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "Default Avatar Factory"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + public void NewClient(IClientAPI client) + { + client.OnAvatarNowWearing += AvatarIsWearing; + } + + public void RemoveClient(IClientAPI client) + { + // client.OnAvatarNowWearing -= AvatarIsWearing; + } + + public void AvatarIsWearing(Object sender, AvatarWearingArgs e) + { + IClientAPI clientView = (IClientAPI)sender; + CachedUserInfo profile = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(clientView.AgentId); + if (profile != null) + { + if (profile.RootFolder != null) + { + if (m_avatarsAppearance.ContainsKey(clientView.AgentId)) + { + AvatarAppearance avatAppearance = null; + lock (m_avatarsAppearance) + { + avatAppearance = m_avatarsAppearance[clientView.AgentId]; + } + + foreach (AvatarWearingArgs.Wearable wear in e.NowWearing) + { + if (wear.Type < 13) + { + if (wear.ItemID == LLUUID.Zero) + { + avatAppearance.Wearables[wear.Type].ItemID = LLUUID.Zero; + avatAppearance.Wearables[wear.Type].AssetID = LLUUID.Zero; + + UpdateDatabase(clientView.AgentId, avatAppearance); + } + else + { + LLUUID assetId; + + InventoryItemBase baseItem = profile.RootFolder.HasItem(wear.ItemID); + if (baseItem != null) + { + assetId = baseItem.assetID; + avatAppearance.Wearables[wear.Type].AssetID = assetId; + avatAppearance.Wearables[wear.Type].ItemID = wear.ItemID; + + UpdateDatabase(clientView.AgentId, avatAppearance); + } + } + } + } + } + } + } + } + + public void UpdateDatabase(LLUUID userID, AvatarAppearance avatAppearance) + { + if (m_enablePersist) + { + m_appearanceMapper.Update(userID.UUID, avatAppearance); + } + } + + public static void GetDefaultAvatarAppearance(out AvatarWearable[] wearables, out byte[] visualParams) + { + visualParams = GetDefaultVisualParams(); + wearables = AvatarWearable.DefaultWearables; + } + + private static byte[] GetDefaultVisualParams() + { + byte[] visualParams; + visualParams = new byte[218]; + for (int i = 0; i < 218; i++) + { + visualParams[i] = 100; + } + return visualParams; + } + } +}*/ diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs new file mode 100644 index 0000000..1281873 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChatModule.cs @@ -0,0 +1,831 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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.IO; +using System.Net.Sockets; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using libsecondlife; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.Chat +{ + public class ChatModule : IRegionModule, ISimChat + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private List m_scenes = new List(); + + private int m_whisperdistance = 10; + private int m_saydistance = 30; + private int m_shoutdistance = 100; + + private IRCChatModule m_irc = null; + + private string m_last_new_user = null; + private string m_last_leaving_user = null; + private string m_defaultzone = null; + internal object m_syncInit = new object(); + internal object m_syncLogout = new object(); + private Thread m_irc_connector=null; + + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_syncInit) + { + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + scene.EventManager.OnNewClient += NewClient; + scene.RegisterModuleInterface(this); + } + + // wrap this in a try block so that defaults will work if + // the config file doesn't specify otherwise. + try + { + m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); + m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); + m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); + } + catch (Exception) + { + } + + try + { + m_defaultzone = config.Configs["IRC"].GetString("nick","Sim"); + } + catch (Exception) + { + } + + // setup IRC Relay + if (m_irc == null) { m_irc = new IRCChatModule(config); } + if (m_irc_connector == null) + { + m_irc_connector = new Thread(IRCConnectRun); + m_irc_connector.Name = "IRCConnectorThread"; + m_irc_connector.IsBackground = true; + } + } + } + + public void PostInitialise() + { + if (m_irc.Enabled) + { + try + { + //m_irc.Connect(m_scenes); + if (m_irc_connector == null) + { + m_irc_connector = new Thread(IRCConnectRun); + m_irc_connector.Name = "IRCConnectorThread"; + m_irc_connector.IsBackground = true; + } + if (!m_irc_connector.IsAlive) + { + m_irc_connector.Start(); + ThreadTracker.Add(m_irc_connector); + } + } + catch (Exception) + { + } + } + } + + public void Close() + { + m_irc.Close(); + } + + public string Name + { + get { return "ChatModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + public void NewClient(IClientAPI client) + { + try + { + client.OnChatFromViewer += SimChat; + + if ((m_irc.Enabled) && (m_irc.Connected)) + { + string clientName = client.FirstName + " " + client.LastName; + // handles simple case. May not work for hundred connecting in per second. + // and the NewClients calles getting interleved + // but filters out multiple reports + if (clientName != m_last_new_user) + { + m_last_new_user = clientName; + string clientRegion = FindClientRegion(client.FirstName, client.LastName); + m_irc.PrivMsg(m_irc.Nick, "Sim", "notices " + clientName + " in "+clientRegion); + } + } + client.OnLogout += ClientLoggedOut; + client.OnConnectionClosed += ClientLoggedOut; + client.OnLogout += ClientLoggedOut; + } + catch (Exception ex) + { + m_log.Error("[IRC]: NewClient exception trap:" + ex.ToString()); + } + } + + public void ClientLoggedOut(IClientAPI client) + { + lock (m_syncLogout) + { + try + { + if ((m_irc.Enabled) && (m_irc.Connected)) + { + string clientName = client.FirstName + " " + client.LastName; + string clientRegion = FindClientRegion(client.FirstName, client.LastName); + // handles simple case. May not work for hundred connecting in per second. + // and the NewClients calles getting interleved + // but filters out multiple reports + if (clientName != m_last_leaving_user) + { + m_last_leaving_user = clientName; + m_irc.PrivMsg(m_irc.Nick, "Sim", "notices " + clientName + " left " + clientRegion); + m_log.Info("[IRC]: IRC watcher notices " + clientName + " left " + clientRegion); + } + } + } + catch (Exception ex) + { + m_log.Error("[IRC]: ClientLoggedOut exception trap:" + ex.ToString()); + } + } + } + + private void TrySendChatMessage(ScenePresence presence, LLVector3 fromPos, LLVector3 regionPos, + LLUUID fromAgentID, string fromName, ChatTypeEnum type, string message) + { + if (!presence.IsChildAgent) + { + LLVector3 fromRegionPos = fromPos + regionPos; + LLVector3 toRegionPos = presence.AbsolutePosition + regionPos; + int dis = Math.Abs((int) Util.GetDistanceTo(toRegionPos, fromRegionPos)); + + if (type == ChatTypeEnum.Whisper && dis > m_whisperdistance || + type == ChatTypeEnum.Say && dis > m_saydistance || + type == ChatTypeEnum.Shout && dis > m_shoutdistance) + { + return; + } + + // TODO: should change so the message is sent through the avatar rather than direct to the ClientView + presence.ControllingClient.SendChatMessage(message, (byte) type, fromPos, fromName, fromAgentID); + } + } + + public void SimChat(Object sender, ChatFromViewerArgs e) + { + // FROM: Sim TO: IRC + + ScenePresence avatar = null; + + //TODO: Move ForEachScenePresence and others into IScene. + Scene scene = (Scene) e.Scene; + + //TODO: Remove the need for this check + if (scene == null) + scene = m_scenes[0]; + + // Filled in since it's easier than rewriting right now. + LLVector3 fromPos = e.Position; + LLVector3 regionPos = new LLVector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); + + string fromName = e.From; + string message = e.Message; + LLUUID fromAgentID = LLUUID.Zero; + + if (e.Sender != null) + { + avatar = scene.GetScenePresence(e.Sender.AgentId); + } + + if (avatar != null) + { + fromPos = avatar.AbsolutePosition; + regionPos = new LLVector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); + fromName = avatar.Firstname + " " + avatar.Lastname; + fromAgentID = e.Sender.AgentId; + } + + // Try to reconnect to server if not connected + if (m_irc.Enabled && !m_irc.Connected) + { + // In a non-blocking way. Eventually the connector will get it started + try + { + if (m_irc_connector == null) + { + m_irc_connector = new Thread(IRCConnectRun); + m_irc_connector.Name = "IRCConnectorThread"; + m_irc_connector.IsBackground = true; + } + if (!m_irc_connector.IsAlive) + { + m_irc_connector.Start(); + ThreadTracker.Add(m_irc_connector); + } + } + catch (Exception) + { + } + } + + + // We only want to relay stuff on channel 0 + if (e.Channel == 0) + { + // IRC stuff + if (e.Message.Length > 0) + { + if (m_irc.Connected && (avatar != null)) // this is to keep objects from talking to IRC + { + m_irc.PrivMsg(fromName, scene.RegionInfo.RegionName, e.Message); + } + } + + foreach (Scene s in m_scenes) + { + s.ForEachScenePresence(delegate(ScenePresence presence) + { + TrySendChatMessage(presence, fromPos, regionPos, + fromAgentID, fromName, e.Type, message); + }); + } + } + } + + // if IRC is enabled then just keep trying using a monitor thread + public void IRCConnectRun() + { + while(true) + { + if ((m_irc.Enabled)&&(!m_irc.Connected)) + { + m_irc.Connect(m_scenes); + } + Thread.Sleep(15000); + } + } + + public string FindClientRegion(string client_FirstName,string client_LastName) + { + string sourceRegion = null; + foreach (Scene s in m_scenes) + { + s.ForEachScenePresence(delegate(ScenePresence presence) + { + if ((presence.IsChildAgent==false) + &&(presence.Firstname==client_FirstName) + &&(presence.Lastname==client_LastName)) + { + sourceRegion = presence.Scene.RegionInfo.RegionName; + //sourceRegion= s.RegionInfo.RegionName; + } + }); + if (sourceRegion != null) return sourceRegion; + } + if (m_defaultzone == null) { m_defaultzone = "Sim"; } + return m_defaultzone; + } + } + + internal class IRCChatModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_server = null; + private uint m_port = 6668; + private string m_user = "USER OpenSimBot 8 * :I'm a OpenSim to irc bot"; + private string m_nick = null; + private string m_basenick = null; + private string m_channel = null; + private string m_privmsgformat = "PRIVMSG {0} :<{1} in {2}>: {3}"; + + private NetworkStream m_stream; + private TcpClient m_tcp; + private StreamWriter m_writer; + private StreamReader m_reader; + + private Thread pingSender; + private Thread listener; + internal object m_syncConnect = new object(); + + private bool m_enabled = false; + private bool m_connected = false; + + private List m_scenes = null; + private List m_last_scenes = null; + + public IRCChatModule(IConfigSource config) + { + m_nick = "OSimBot" + Util.RandomClass.Next(1, 99); + m_tcp = null; + m_writer = null; + m_reader = null; + + // configuration in OpenSim.ini + // [IRC] + // server = chat.freenode.net + // nick = OSimBot_mysim + // ;username = USER OpenSimBot 8 * :I'm a OpenSim to irc bot + // ; username is the IRC command line sent + // ; USER * : + // channel = #opensim-regions + // port = 6667 + // ;MSGformat fields : 0=botnick, 1=user, 2=region, 3=message + // ;for : : + // ;msgformat = "PRIVMSG {0} :<{1} in {2}>: {3}" + // ;for : - : + // ;msgformat = "PRIVMSG {0} : {3} - {1} of {2}" + // ;for : - from : + // ;msgformat = "PRIVMSG {0} : {3} - from {1}" + // Traps I/O disconnects so it does not crash the sim + // Trys to reconnect if disconnected and someone says something + // Tells IRC server "QUIT" when doing a close (just to be nice) + // Default port back to 6667 + + try + { + m_server = config.Configs["IRC"].GetString("server"); + m_nick = config.Configs["IRC"].GetString("nick"); + m_basenick = m_nick; + m_channel = config.Configs["IRC"].GetString("channel"); + m_port = (uint) config.Configs["IRC"].GetInt("port", (int) m_port); + m_user = config.Configs["IRC"].GetString("username", m_user); + m_privmsgformat = config.Configs["IRC"].GetString("msgformat", m_privmsgformat); + if (m_server != null && m_nick != null && m_channel != null) + { + m_nick = m_nick + Util.RandomClass.Next(1, 99); + m_enabled = true; + } + } + catch (Exception) + { + m_log.Info("[CHAT]: No IRC config information, skipping IRC bridge configuration"); + } + } + + public bool Connect(List scenes) + { + lock (m_syncConnect) + { + try + { + if (m_connected) return true; + m_scenes = scenes; + if (m_last_scenes == null) { m_last_scenes = scenes; } + + m_tcp = new TcpClient(m_server, (int)m_port); + m_log.Info("[IRC]: Connecting..."); + m_stream = m_tcp.GetStream(); + m_log.Info("[IRC]: Connected to " + m_server); + m_reader = new StreamReader(m_stream); + m_writer = new StreamWriter(m_stream); + + pingSender = new Thread(new ThreadStart(PingRun)); + pingSender.Name = "PingSenderThread"; + pingSender.IsBackground = true; + pingSender.Start(); + ThreadTracker.Add(pingSender); + + listener = new Thread(new ThreadStart(ListenerRun)); + listener.Name = "IRCChatModuleListenerThread"; + listener.IsBackground = true; + listener.Start(); + ThreadTracker.Add(listener); + + m_writer.WriteLine(m_user); + m_writer.Flush(); + m_writer.WriteLine("NICK " + m_nick); + m_writer.Flush(); + m_writer.WriteLine("JOIN " + m_channel); + m_writer.Flush(); + m_log.Info("[IRC]: Connection fully established"); + m_connected = true; + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + return m_connected; + } + } + + public bool Enabled + { + get { return m_enabled; } + } + + public bool Connected + { + get { return m_connected; } + } + + public string Nick + { + get { return m_nick; } + } + + public void Reconnect() + { + m_connected = false; + listener.Abort(); + pingSender.Abort(); + m_writer.Close(); + m_reader.Close(); + m_tcp.Close(); + if (m_enabled) { Connect(m_last_scenes); } + } + + public void PrivMsg(string from, string region, string msg) + { + // One message to the IRC server + + try + { + if (m_privmsgformat == null) + { + m_writer.WriteLine("PRIVMSG {0} :<{1} in {2}>: {3}", m_channel, from, region, msg); + } + else + { + m_writer.WriteLine(m_privmsgformat, m_channel, from, region, msg); + } + m_writer.Flush(); + m_log.Info("[IRC]: PrivMsg " + from + " in " + region + " :" + msg); + } + catch (IOException) + { + m_log.Error("[IRC]: Disconnected from IRC server.(PrivMsg)"); + Reconnect(); + } + catch (Exception ex) + { + m_log.Error("[IRC]: PrivMsg exception trap:" + ex.ToString()); + } + } + + private Dictionary ExtractMsg(string input) + { + //examines IRC commands and extracts any private messages + // which will then be reboadcast in the Sim + + m_log.Info("[IRC]: ExtractMsg: " + input); + Dictionary result = null; + //string regex = @":(?\w*)!~(?\S*) PRIVMSG (?\S+) :(?.*)"; + string regex = @":(?\w*)!(?\S*) PRIVMSG (?\S+) :(?.*)"; + Regex RE = new Regex(regex, RegexOptions.Multiline); + MatchCollection matches = RE.Matches(input); + // Get some direct matches $1 $4 is a + if ((matches.Count == 1) && (matches[0].Groups.Count == 5)) + { + result = new Dictionary(); + result.Add("nick", matches[0].Groups[1].Value); + result.Add("user", matches[0].Groups[2].Value); + result.Add("channel", matches[0].Groups[3].Value); + result.Add("msg", matches[0].Groups[4].Value); + } + else + { + m_log.Info("[IRC]: Number of matches: " + matches.Count); + if (matches.Count > 0) + { + m_log.Info("[IRC]: Number of groups: " + matches[0].Groups.Count); + } + } + return result; + } + + public void PingRun() + { + // IRC keep alive thread + // send PING ever 15 seconds + while (true) + { + try + { + if (m_connected == true) + { + m_writer.WriteLine("PING :" + m_server); + m_writer.Flush(); + Thread.Sleep(15000); + } + } + catch (IOException) + { + m_log.Error("[IRC]: Disconnected from IRC server.(PingRun)"); + Reconnect(); + } + catch (Exception ex) + { + m_log.Error("[IRC]: PingRun exception trap:" + ex.ToString() + "\n" + ex.StackTrace); + } + } + } + + public void ListenerRun() + { + string inputLine; + LLVector3 pos = new LLVector3(128, 128, 20); + while (true) + { + try + { + while ((m_connected == true) && ((inputLine = m_reader.ReadLine()) != null)) + { + // Console.WriteLine(inputLine); + if (inputLine.Contains(m_channel)) + { + Dictionary data = ExtractMsg(inputLine); + // Any chat ??? + if (data != null) + { + foreach (Scene m_scene in m_scenes) + { + m_scene.ForEachScenePresence(delegate(ScenePresence avatar) + { + if (!avatar.IsChildAgent) + { + avatar.ControllingClient.SendChatMessage( + Helpers.StringToField(data["msg"]), 255, + pos, data["nick"], + LLUUID.Zero); + } + }); + } + } + else + { + // Was an command from the IRC server + ProcessIRCCommand(inputLine); + } + } + else + { + // Was an command from the IRC server + ProcessIRCCommand(inputLine); + } + Thread.Sleep(150); + } + } + catch (IOException) + { + m_log.Error("[IRC]: ListenerRun IOException. Disconnected from IRC server ??? (ListenerRun)"); + Reconnect(); + } + catch (Exception ex) + { + m_log.Error("[IRC]: ListenerRun exception trap:" + ex.ToString() + "\n" + ex.StackTrace); + } + } + } + + public void BroadcastSim(string message,string sender) + { + LLVector3 pos = new LLVector3(128, 128, 20); + try + { + foreach (Scene m_scene in m_scenes) + { + m_scene.ForEachScenePresence(delegate(ScenePresence avatar) + { + if (!avatar.IsChildAgent) + { + avatar.ControllingClient.SendChatMessage( + Helpers.StringToField(message), 255, + pos, sender, + LLUUID.Zero); + } + }); + } + } + catch (Exception ex) // IRC gate should not crash Sim + { + m_log.Error("[IRC]: BroadcastSim Exception Trap:" + ex.ToString() + "\n" + ex.StackTrace); + } + } + + public enum ErrorReplies + { + NotRegistered = 451, // ":You have not registered" + NicknameInUse = 433 // " :Nickname is already in use" + } + + public enum Replies + { + MotdStart = 375, // ":- Message of the day - " + Motd = 372, // ":- " + EndOfMotd = 376 // ":End of /MOTD command" + } + + public void ProcessIRCCommand(string command) + { + //m_log.Info("[IRC]: ProcessIRCCommand:" + command); + + string[] commArgs = new string[command.Split(' ').Length]; + string c_server = m_server; + + commArgs = command.Split(' '); + if (commArgs[0].Substring(0, 1) == ":") + { + commArgs[0] = commArgs[0].Remove(0, 1); + } + + if (commArgs[1] == "002") + { + // fetch the correct servername + // ex: irc.freenode.net -> brown.freenode.net/kornbluth.freenode.net/... + // irc.bluewin.ch -> irc1.bluewin.ch/irc2.bluewin.ch + + c_server = (commArgs[6].Split('['))[0]; + m_server = c_server; + } + + if (commArgs[0] == "ERROR") + { + m_log.Error("[IRC]: IRC SERVER ERROR:" + command); + } + + if (commArgs[0] == "PING") + { + string p_reply = ""; + + for (int i = 1; i < commArgs.Length; i++) + { + p_reply += commArgs[i] + " "; + } + + m_writer.WriteLine("PONG " + p_reply); + m_writer.Flush(); + } + else if (commArgs[0] == c_server) + { + // server message + try + { + Int32 commandCode = Int32.Parse(commArgs[1]); + switch (commandCode) + { + case (int)ErrorReplies.NicknameInUse: + // Gen a new name + m_nick = m_basenick + Util.RandomClass.Next(1, 99); + m_log.Error("[IRC]: IRC SERVER reports NicknameInUse, trying " + m_nick); + // Retry + m_writer.WriteLine("NICK " + m_nick); + m_writer.Flush(); + m_writer.WriteLine("JOIN " + m_channel); + m_writer.Flush(); + break; + case (int)ErrorReplies.NotRegistered: + break; + case (int)Replies.EndOfMotd: + break; + } + } + catch (Exception) + { + } + } + else + { + // Normal message + string commAct = commArgs[1]; + switch (commAct) + { + case "JOIN": eventIrcJoin(commArgs); break; + case "PART": eventIrcPart(commArgs); break; + case "MODE": eventIrcMode(commArgs); break; + case "NICK": eventIrcNickChange(commArgs); break; + case "KICK": eventIrcKick(commArgs); break; + case "QUIT": eventIrcQuit(commArgs); break; + case "PONG": break; // that's nice + } + } + } + + public void eventIrcJoin(string[] commArgs) + { + string IrcChannel = commArgs[2]; + string IrcUser = commArgs[0].Split('!')[0]; + BroadcastSim(IrcUser + " is joining " + IrcChannel, m_nick); + } + + public void eventIrcPart(string[] commArgs) + { + string IrcChannel = commArgs[2]; + string IrcUser = commArgs[0].Split('!')[0]; + BroadcastSim(IrcUser + " is parting " + IrcChannel, m_nick); + } + + public void eventIrcMode(string[] commArgs) + { + string IrcChannel = commArgs[2]; + string IrcUser = commArgs[0].Split('!')[0]; + string UserMode = ""; + for (int i = 3; i < commArgs.Length; i++) + { + UserMode += commArgs[i] + " "; + } + + if (UserMode.Substring(0, 1) == ":") + { + UserMode = UserMode.Remove(0, 1); + } + } + + public void eventIrcNickChange(string[] commArgs) + { + string UserOldNick = commArgs[0].Split('!')[0]; + string UserNewNick = commArgs[2].Remove(0, 1); + BroadcastSim(UserOldNick + " changed their nick to " + UserNewNick, m_nick); + } + + public void eventIrcKick(string[] commArgs) + { + string UserKicker = commArgs[0].Split('!')[0]; + string UserKicked = commArgs[3]; + string IrcChannel = commArgs[2]; + string KickMessage = ""; + for (int i = 4; i < commArgs.Length; i++) + { + KickMessage += commArgs[i] + " "; + } + BroadcastSim(UserKicker + " kicked " + UserKicked +" on "+IrcChannel+" saying "+KickMessage, m_nick); + if (UserKicked == m_nick) + { + BroadcastSim("Hey, that was me!!!", m_nick); + } + } + + public void eventIrcQuit(string[] commArgs) + { + string IrcUser = commArgs[0].Split('!')[0]; + string QuitMessage = ""; + + for (int i = 2; i < commArgs.Length; i++) + { + QuitMessage += commArgs[i] + " "; + } + BroadcastSim(IrcUser + " quits saying " + QuitMessage, m_nick); + } + + public void Close() + { + m_connected = false; + m_writer.WriteLine("QUIT :" + m_nick + " to " + m_channel + " wormhole with " + m_server + " closing"); + m_writer.Flush(); + listener.Abort(); + pingSender.Abort(); + m_writer.Close(); + m_reader.Close(); + m_tcp.Close(); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs new file mode 100644 index 0000000..0e058ec --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs @@ -0,0 +1,1491 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Xml; +using libsecondlife; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.Currency.SampleMoney +{ + /// + /// Demo Economy/Money Module. This is not a production quality money/economy module! + /// This is a demo for you to use when making one that works for you. + /// // To use the following you need to add: + /// -helperuri
+ /// to the command line parameters you use to start up your client + /// This commonly looks like -helperuri http://127.0.0.1:9000/ + /// + /// Centralized grid structure example using OpenSimWi Redux revision 9+ + /// svn co https://opensimwiredux.svn.sourceforge.net/svnroot/opensimwiredux + ///
+ + public delegate void ObjectPaid(LLUUID objectID, LLUUID agentID, int amount); + + public interface IMoneyModule : IRegionModule + { + bool ObjectGiveMoney(LLUUID objectID, LLUUID fromID, LLUUID toID, int amount); + + event ObjectPaid OnObjectPaid; + } + + public class SampleMoneyModule : IMoneyModule + { + public event ObjectPaid OnObjectPaid; + + private ObjectPaid handerOnObjectPaid; + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Region UUIDS indexed by AgentID + /// + Dictionary m_rootAgents = new Dictionary(); + + /// + /// Scenes by Region Handle + /// + private Dictionary m_scenel = new Dictionary(); + + private IConfigSource m_gConfig; + + private bool m_keepMoneyAcrossLogins = true; + + private int m_minFundsBeforeRefresh = 100; + + private int m_stipend = 1000; + + private bool m_enabled = true; + + private Dictionary m_KnownClientFunds = new Dictionary(); + + private bool gridmode = false; + private Scene XMLRPCHandler; + private float EnergyEfficiency = 0f; + private int ObjectCapacity = 45000; + private int ObjectCount = 0; + private int PriceEnergyUnit = 0; + private int PriceGroupCreate = 0; + private int PriceObjectClaim = 0; + private float PriceObjectRent = 0f; + private float PriceObjectScaleFactor = 0f; + private int PriceParcelClaim = 0; + private float PriceParcelClaimFactor = 0f; + private int PriceParcelRent = 0; + private int PricePublicObjectDecay = 0; + private int PricePublicObjectDelete = 0; + private int PriceRentLight = 0; + private int PriceUpload = 0; + private int TeleportMinPrice = 0; + private int UserLevelPaysFees = 2; + private string m_MoneyAddress = String.Empty; + private string m_LandAddress = String.Empty; + + float TeleportPriceExponent = 0f; + + /// + /// Where Stipends come from and Fees go to. + /// + LLUUID EconomyBaseAccount = LLUUID.Zero; + + /// + /// Startup + /// + /// + /// + public void Initialise(Scene scene, IConfigSource config) + { + m_gConfig = config; + + IConfig startupConfig = m_gConfig.Configs["Startup"]; + IConfig economyConfig = m_gConfig.Configs["Economy"]; + + scene.RegisterModuleInterface(this); + + ReadConfigAndPopulate(scene, startupConfig, "Startup"); + ReadConfigAndPopulate(scene, economyConfig, "Economy"); + + if (m_enabled) + { + lock (m_scenel) + { + if (m_scenel.Count == 0) + { + XMLRPCHandler = scene; + + // To use the following you need to add: + // -helperuri
+ // to the command line parameters you use to start up your client + // This commonly looks like -helperuri http://127.0.0.1:9000/ + + if (m_MoneyAddress.Length > 0) + { + // Centralized grid structure using OpenSimWi Redux revision 9+ + // https://opensimwiredux.svn.sourceforge.net/svnroot/opensimwiredux + scene.AddXmlRPCHandler("balanceUpdateRequest", GridMoneyUpdate); + scene.AddXmlRPCHandler("userAlert", UserAlert); + } + else + { + // Local Server.. enables functionality only. + scene.AddXmlRPCHandler("getCurrencyQuote", quote_func); + scene.AddXmlRPCHandler("buyCurrency", buy_func); + scene.AddXmlRPCHandler("preflightBuyLandPrep", preflightBuyLandPrep_func); + scene.AddXmlRPCHandler("buyLandPrep", landBuy_func); + } + + + } + + if (m_scenel.ContainsKey(scene.RegionInfo.RegionHandle)) + { + m_scenel[scene.RegionInfo.RegionHandle] = scene; + } + else + { + m_scenel.Add(scene.RegionInfo.RegionHandle, scene); + } + } + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnMoneyTransfer += MoneyTransferAction; + scene.EventManager.OnClientClosed += ClientClosed; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnClientClosed += ClientLoggedOut; + scene.EventManager.OnValidateLandBuy += ValidateLandBuy; + scene.EventManager.OnLandBuy += processLandBuy; + + } + } + /// + /// Parse Configuration + /// + /// + /// + /// + private void ReadConfigAndPopulate(Scene scene, IConfig startupConfig, string config) + { + if (config == "Startup" && startupConfig != null) + { + gridmode = startupConfig.GetBoolean("gridmode", false); + m_enabled = (startupConfig.GetString("economymodule", "BetaGridLikeMoneyModule") == "BetaGridLikeMoneyModule"); + } + + if (config == "Economy" && startupConfig != null) + { + ObjectCapacity = startupConfig.GetInt("ObjectCapacity", 45000); + PriceEnergyUnit = startupConfig.GetInt("PriceEnergyUnit", 100); + PriceObjectClaim = startupConfig.GetInt("PriceObjectClaim", 10); + PricePublicObjectDecay = startupConfig.GetInt("PricePublicObjectDecay", 4); + PricePublicObjectDelete = startupConfig.GetInt("PricePublicObjectDelete", 4); + PriceParcelClaim = startupConfig.GetInt("PriceParcelClaim", 1); + PriceParcelClaimFactor = startupConfig.GetFloat("PriceParcelClaimFactor", 1f); + PriceUpload = startupConfig.GetInt("PriceUpload", 0); + PriceRentLight = startupConfig.GetInt("PriceRentLight", 5); + TeleportMinPrice = startupConfig.GetInt("TeleportMinPrice", 2); + TeleportPriceExponent = startupConfig.GetFloat("TeleportPriceExponent", 2f); + EnergyEfficiency = startupConfig.GetFloat("EnergyEfficiency", 1); + PriceObjectRent = startupConfig.GetFloat("PriceObjectRent", 1); + PriceObjectScaleFactor = startupConfig.GetFloat("PriceObjectScaleFactor", 10); + PriceParcelRent = startupConfig.GetInt("PriceParcelRent", 1); + PriceGroupCreate = startupConfig.GetInt("PriceGroupCreate", -1); + string EBA = startupConfig.GetString("EconomyBaseAccount", LLUUID.Zero.ToString()); + Helpers.TryParse(EBA,out EconomyBaseAccount); + + UserLevelPaysFees = startupConfig.GetInt("UserLevelPaysFees", -1); + m_stipend = startupConfig.GetInt("UserStipend", 500); + m_minFundsBeforeRefresh = startupConfig.GetInt("IssueStipendWhenClientIsBelowAmount", 10); + m_keepMoneyAcrossLogins = startupConfig.GetBoolean("KeepMoneyAcrossLogins", true); + m_MoneyAddress = startupConfig.GetString("CurrencyServer", String.Empty); + m_LandAddress = startupConfig.GetString("LandServer", String.Empty); + } + + // Send ObjectCapacity to Scene.. Which sends it to the SimStatsReporter. + scene.SetObjectCapacity(ObjectCapacity); + } + + /// + /// New Client Event Handler + /// + /// + private void OnNewClient(IClientAPI client) + { + // Here we check if we're in grid mode + // I imagine that the 'check balance' + // function for the client should be here or shortly after + + if (gridmode) + { + if (m_MoneyAddress.Length == 0) + { + + CheckExistAndRefreshFunds(client.AgentId); + } + else + { + bool childYN = true; + ScenePresence agent = null; + //client.SecureSessionId; + Scene s = LocateSceneClientIn(client.AgentId); + if (s != null) + { + agent = s.GetScenePresence(client.AgentId); + if (agent != null) + childYN = agent.IsChildAgent; + } + if (s != null && agent != null && childYN == false) + { + //s.RegionInfo.RegionHandle; + LLUUID agentID = LLUUID.Zero; + int funds = 0; + + Hashtable hbinfo = GetBalanceForUserFromMoneyServer(client.AgentId, client.SecureSessionId, s.RegionInfo.originRegionID.ToString(), s.RegionInfo.regionSecret); + if ((bool)hbinfo["success"] == true) + { + + Helpers.TryParse((string)hbinfo["agentId"], out agentID); + try + { + funds = (Int32)hbinfo["funds"]; + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + m_log.ErrorFormat("[MONEY]: While getting the Currency for user {0}, the return funds overflowed.", agentID); + client.SendAlertMessage("Unable to get your money balance, money operations will be unavailable"); + } + catch (InvalidCastException) + { + funds = 0; + } + + m_KnownClientFunds[agentID] = funds; + } + else + { + m_log.WarnFormat("[MONEY]: Getting Money for user {0} failed with the following message:{1}", agentID, (string)hbinfo["errorMessage"]); + client.SendAlertMessage((string)hbinfo["errorMessage"]); + } + SendMoneyBalance(client, agentID, client.SessionId, LLUUID.Zero); + + } + } + + } + else + { + CheckExistAndRefreshFunds(client.AgentId); + } + + // Subscribe to Money messages + client.OnEconomyDataRequest += EconomyDataRequestHandler; + client.OnMoneyBalanceRequest += SendMoneyBalance; + client.OnRequestPayPrice += requestPayPrice; + client.OnLogout += ClientClosed; + + + } + + #region event Handlers + + public void requestPayPrice(IClientAPI client, LLUUID objectID) + { + Scene scene=LocateSceneClientIn(client.AgentId); + if(scene == null) + return; + + SceneObjectPart task=scene.GetSceneObjectPart(objectID); + if(task == null) + return; + SceneObjectGroup group=task.ParentGroup; + SceneObjectPart root=group.RootPart; + + client.SendPayPrice(objectID, root.PayPrice); + } + + /// + /// When the client closes the connection we remove their accounting info from memory to free up resources. + /// + /// + public void ClientClosed(LLUUID AgentID) + { + lock (m_KnownClientFunds) + { + if (m_keepMoneyAcrossLogins && m_MoneyAddress.Length == 0) + { + } + else + { + m_KnownClientFunds.Remove(AgentID); + } + } + } + + /// + /// Event called Economy Data Request handler. + /// + /// + public void EconomyDataRequestHandler(LLUUID agentId) + { + IClientAPI user = LocateClientObject(agentId); + + if (user != null) + { + user.SendEconomyData(EnergyEfficiency, ObjectCapacity, ObjectCount, PriceEnergyUnit, PriceGroupCreate, + PriceObjectClaim, PriceObjectRent, PriceObjectScaleFactor, PriceParcelClaim, PriceParcelClaimFactor, + PriceParcelRent, PricePublicObjectDecay, PricePublicObjectDelete, PriceRentLight, PriceUpload, + TeleportMinPrice, TeleportPriceExponent); + } + } + + private void ValidateLandBuy (Object osender, EventManager.LandBuyArgs e) + { + if (m_MoneyAddress.Length == 0) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(e.agentId)) + { + // Does the sender have enough funds to give? + if (m_KnownClientFunds[e.agentId] >= e.parcelPrice) + { + lock(e) + { + e.economyValidated=true; + } + } + } + } + } + else + { + if(GetRemoteBalance(e.agentId) >= e.parcelPrice) + { + lock(e) + { + e.economyValidated=true; + } + } + } + } + + private void processLandBuy(Object osender, EventManager.LandBuyArgs e) + { + lock(e) + { + if(e.economyValidated == true && e.transactionID == 0) + { + e.transactionID=Util.UnixTimeSinceEpoch(); + + if(doMoneyTransfer(e.agentId, e.parcelOwnerID, e.parcelPrice, 0, "Land purchase")) + { + lock (e) + { + e.amountDebited = e.parcelPrice; + } + } + } + } + } + + /// + /// THis method gets called when someone pays someone else as a gift. + /// + /// + /// + private void MoneyTransferAction (Object osender, EventManager.MoneyTransferArgs e) + { + IClientAPI sender = null; + IClientAPI receiver = null; + + if(m_MoneyAddress.Length > 0) // Handled on server + e.description=String.Empty; + + if(e.transactiontype == 5008) // Object gets paid + { + sender = LocateClientObject(e.sender); + if (sender != null) + { + SceneObjectPart part=findPrim(e.receiver); + if(part == null) + return; + + string name=resolveAgentName(part.OwnerID); + if(name == String.Empty) + name="(hippos)"; + + receiver = LocateClientObject(part.OwnerID); + + string description=String.Format("Paid {0} via object {1}", name, e.description); + bool transactionresult = doMoneyTransfer(e.sender, part.OwnerID, e.amount, e.transactiontype, description); + + if(transactionresult) + { + ObjectPaid handlerOnObjectPaid = OnObjectPaid; + if(handlerOnObjectPaid != null) + { + handlerOnObjectPaid(e.receiver, e.sender, e.amount); + } + } + + if (e.sender != e.receiver) + { + sender.SendMoneyBalance(LLUUID.Random(), transactionresult, Helpers.StringToField(e.description), GetFundsForAgentID(e.sender)); + } + if(receiver != null) + { + receiver.SendMoneyBalance(LLUUID.Random(), transactionresult, Helpers.StringToField(e.description), GetFundsForAgentID(part.OwnerID)); + } + } + return; + } + + sender = LocateClientObject(e.sender); + if (sender != null) + { + receiver = LocateClientObject(e.receiver); + + bool transactionresult = doMoneyTransfer(e.sender, e.receiver, e.amount, e.transactiontype, e.description); + + if (e.sender != e.receiver) + { + if (sender != null) + { + sender.SendMoneyBalance(LLUUID.Random(), transactionresult, Helpers.StringToField(e.description), GetFundsForAgentID(e.sender)); + } + } + + if (receiver != null) + { + receiver.SendMoneyBalance(LLUUID.Random(), transactionresult, Helpers.StringToField(e.description), GetFundsForAgentID(e.receiver)); + } + } + else + { + m_log.Warn("[MONEY]: Potential Fraud Warning, got money transfer request for avatar that isn't in this simulator - Details; Sender:" + e.sender.ToString() + " Receiver: " + e.receiver.ToString() + " Amount: " + e.amount.ToString()); + } + } + + /// + /// Event Handler for when a root agent becomes a child agent + /// + /// + private void MakeChildAgent(ScenePresence avatar) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (m_rootAgents[avatar.UUID] == avatar.Scene.RegionInfo.originRegionID) + { + m_rootAgents.Remove(avatar.UUID); + m_log.Info("[MONEY]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent"); + } + + } + } + + } + + /// + /// Event Handler for when the client logs out. + /// + /// + private void ClientLoggedOut(LLUUID AgentId) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(AgentId)) + { + m_rootAgents.Remove(AgentId); + //m_log.Info("[MONEY]: Removing " + AgentId + ". Agent logged out."); + } + } + } + + /// + /// Call this when the client disconnects. + /// + /// + public void ClientClosed(IClientAPI client) + { + ClientClosed(client.AgentId); + } + + /// + /// Event Handler for when an Avatar enters one of the parcels in the simulator. + /// + /// + /// + /// + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, LLUUID regionID) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (avatar.Scene.RegionInfo.originRegionID != m_rootAgents[avatar.UUID]) + { + m_rootAgents[avatar.UUID] = avatar.Scene.RegionInfo.originRegionID; + //m_log.Info("[MONEY]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + // Claim User! my user! Mine mine mine! + if (m_MoneyAddress.Length > 0) + { + Scene RegionItem = GetSceneByUUID(regionID); + if (RegionItem != null) + { + Hashtable hresult = claim_user(avatar.UUID, avatar.ControllingClient.SecureSessionId, regionID, RegionItem.RegionInfo.regionSecret); + if ((bool)hresult["success"] == true) + { + int funds = 0; + try + { + funds = (Int32)hresult["funds"]; + } + catch (InvalidCastException) + { + + } + SetLocalFundsForAgentID(avatar.UUID, funds); + } + else + { + avatar.ControllingClient.SendAgentAlertMessage((string)hresult["errorMessage"], true); + } + } + } + } + } + else + { + lock (m_rootAgents) + { + m_rootAgents.Add(avatar.UUID, avatar.Scene.RegionInfo.originRegionID); + } + if (m_MoneyAddress.Length > 0) + { + Scene RegionItem = GetSceneByUUID(regionID); + if (RegionItem != null) + { + Hashtable hresult = claim_user(avatar.UUID, avatar.ControllingClient.SecureSessionId, regionID, RegionItem.RegionInfo.regionSecret); + if ((bool)hresult["success"] == true) + { + int funds = 0; + try + { + funds = (Int32)hresult["funds"]; + } + catch (InvalidCastException) + { + + } + SetLocalFundsForAgentID(avatar.UUID, funds); + } + else + { + avatar.ControllingClient.SendAgentAlertMessage((string)hresult["errorMessage"], true); + } + } + } + + //m_log.Info("[MONEY]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + } + } + //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); + } + + #endregion + + /// + /// Transfer money + /// + /// + /// + /// + /// + private bool doMoneyTransfer(LLUUID Sender, LLUUID Receiver, int amount, int transactiontype, string description) + { + bool result = false; + if (amount >= 0) + { + lock (m_KnownClientFunds) + { + // If we don't know about the sender, then the sender can't + // actually be here and therefore this is likely fraud or outdated. + if (m_MoneyAddress.Length == 0) + { + if (m_KnownClientFunds.ContainsKey(Sender)) + { + // Does the sender have enough funds to give? + if (m_KnownClientFunds[Sender] >= amount) + { + // Subtract the funds from the senders account + m_KnownClientFunds[Sender] -= amount; + + // do we know about the receiver? + if (!m_KnownClientFunds.ContainsKey(Receiver)) + { + // Make a record for them so they get the updated balance when they login + CheckExistAndRefreshFunds(Receiver); + } + if (m_enabled) + { + //Add the amount to the Receiver's funds + m_KnownClientFunds[Receiver] += amount; + result = true; + } + } + else + { + // These below are redundant to make this clearer to read + result = false; + } + } + else + { + result = false; + } + } + else + { + result = TransferMoneyonMoneyServer(Sender, Receiver, amount, transactiontype, description); + } + } + } + return result; + } + + #region Utility Helpers + /// + /// Locates a IClientAPI for the client specified + /// + /// + /// + private IClientAPI LocateClientObject(LLUUID AgentID) + { + ScenePresence tPresence = null; + IClientAPI rclient = null; + + lock (m_scenel) + { + foreach (Scene _scene in m_scenel.Values) + { + tPresence = _scene.GetScenePresence(AgentID); + if (tPresence != null) + { + if (!tPresence.IsChildAgent) + { + rclient = tPresence.ControllingClient; + } + } + if (rclient != null) + { + return rclient; + } + } + + } + return null; + } + + private Scene LocateSceneClientIn(LLUUID AgentId) + { + lock (m_scenel) + { + foreach (Scene _scene in m_scenel.Values) + { + ScenePresence tPresence = _scene.GetScenePresence(AgentId); + if (tPresence != null) + { + if (!tPresence.IsChildAgent) + { + return _scene; + } + } + + } + + } + return null; + } + + /// + /// Utility function Gets a Random scene in the instance. For when which scene exactly you're doing something with doesn't matter + /// + /// + public Scene GetRandomScene() + { + lock (m_scenel) + { + foreach (Scene rs in m_scenel.Values) + return rs; + } + return null; + + } + /// + /// Utility function to get a Scene by RegionID in a module + /// + /// + /// + public Scene GetSceneByUUID(LLUUID RegionID) + { + lock (m_scenel) + { + foreach (Scene rs in m_scenel.Values) + { + if (rs.RegionInfo.originRegionID == RegionID) + { + return rs; + } + } + } + return null; + } + #endregion + + + + /// + /// Sends the the stored money balance to the client + /// + /// + /// + /// + /// + public void SendMoneyBalance(IClientAPI client, LLUUID agentID, LLUUID SessionID, LLUUID TransactionID) + { + if (client.AgentId == agentID && client.SessionId == SessionID) + { + int returnfunds = 0; + + try + { + returnfunds = GetFundsForAgentID(agentID); + } + catch (Exception e) + { + client.SendAlertMessage(e.Message + " "); + } + + client.SendMoneyBalance(TransactionID, true, new byte[0], returnfunds); + } + else + { + client.SendAlertMessage("Unable to send your money balance to you!"); + } + } + + #region local Fund Management + /// + /// Ensures that the agent accounting data is set up in this instance. + /// + /// + private void CheckExistAndRefreshFunds(LLUUID agentID) + { + lock (m_KnownClientFunds) + { + if (!m_KnownClientFunds.ContainsKey(agentID)) + { + m_KnownClientFunds.Add(agentID, m_stipend); + } + else + { + if (m_KnownClientFunds[agentID] <= m_minFundsBeforeRefresh) + { + m_KnownClientFunds[agentID] = m_stipend; + } + } + } + } + /// + /// Gets the amount of Funds for an agent + /// + /// + /// + private int GetFundsForAgentID(LLUUID AgentID) + { + int returnfunds = 0; + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(AgentID)) + { + returnfunds = m_KnownClientFunds[AgentID]; + } + else + { + //throw new Exception("Unable to get funds."); + } + } + return returnfunds; + } + private void SetLocalFundsForAgentID(LLUUID AgentID, int amount) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(AgentID)) + { + m_KnownClientFunds[AgentID] = amount; + } + else + { + m_KnownClientFunds.Add(AgentID, amount); + } + } + + } + + #endregion + + /// + /// Gets the current balance for the user from the Grid Money Server + /// + /// + /// + /// + /// + /// + public Hashtable GetBalanceForUserFromMoneyServer(LLUUID agentId, LLUUID secureSessionID, LLUUID regionId, string regionSecret) + { + + Hashtable MoneyBalanceRequestParams = new Hashtable(); + MoneyBalanceRequestParams["agentId"] = agentId.ToString(); + MoneyBalanceRequestParams["secureSessionId"] = secureSessionID.ToString(); + MoneyBalanceRequestParams["regionId"] = regionId.ToString(); + MoneyBalanceRequestParams["secret"] = regionSecret; + MoneyBalanceRequestParams["currencySecret"] = ""; // per - region/user currency secret gotten from the money system + + Hashtable MoneyRespData = genericCurrencyXMLRPCRequest(MoneyBalanceRequestParams, "simulatorUserBalanceRequest"); + + return MoneyRespData; + } + + + + /// + /// Generic XMLRPC client abstraction + /// + /// Hashtable containing parameters to the method + /// Method to invoke + /// Hashtable with success=>bool and other values + public Hashtable genericCurrencyXMLRPCRequest(Hashtable ReqParams, string method) + { + ArrayList SendParams = new ArrayList(); + SendParams.Add(ReqParams); + // Send Request + XmlRpcResponse MoneyResp; + try + { + XmlRpcRequest BalanceRequestReq = new XmlRpcRequest(method, SendParams); + MoneyResp = BalanceRequestReq.Send(m_MoneyAddress, 30000); + } + catch (WebException ex) + { + + m_log.ErrorFormat( + "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", + m_MoneyAddress, ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + //throw (ex); + } + catch (SocketException ex) + { + + m_log.ErrorFormat( + "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", + m_MoneyAddress, ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + //throw (ex); + } + catch (XmlException ex) + { + m_log.ErrorFormat( + "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", + m_MoneyAddress, ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + + } + if (MoneyResp.IsFault) + { + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + + } + Hashtable MoneyRespData = (Hashtable)MoneyResp.Value; + + return MoneyRespData; + } + /// + /// This informs the Money Grid Server that the avatar is in this simulator + /// + /// + /// + /// + /// + /// + public Hashtable claim_user(LLUUID agentId, LLUUID secureSessionID, LLUUID regionId, string regionSecret) + { + + Hashtable MoneyBalanceRequestParams = new Hashtable(); + MoneyBalanceRequestParams["agentId"] = agentId.ToString(); + MoneyBalanceRequestParams["secureSessionId"] = secureSessionID.ToString(); + MoneyBalanceRequestParams["regionId"] = regionId.ToString(); + MoneyBalanceRequestParams["secret"] = regionSecret; + + Hashtable MoneyRespData = genericCurrencyXMLRPCRequest(MoneyBalanceRequestParams, "simulatorClaimUserRequest"); + IClientAPI sendMoneyBal = LocateClientObject(agentId); + if (sendMoneyBal != null) + { + SendMoneyBalance(sendMoneyBal, agentId, sendMoneyBal.SessionId, LLUUID.Zero); + } + return MoneyRespData; + } + + private SceneObjectPart findPrim(LLUUID objectID) + { + lock (m_scenel) + { + foreach (Scene s in m_scenel.Values) + { + SceneObjectPart part=s.GetSceneObjectPart(objectID); + if(part != null) + { + return part; + } + } + } + return null; + } + + private string resolveObjectName(LLUUID objectID) + { + SceneObjectPart part=findPrim(objectID); + if(part != null) + { + return part.Name; + } + return String.Empty; + } + + private string resolveAgentName(LLUUID agentID) + { + // try avatar username surname + Scene scene=GetRandomScene(); + UserProfileData profile = scene.CommsManager.UserService.GetUserProfile(agentID); + if (profile != null) + { + string avatarname = profile.FirstName + " " + profile.SurName; + return avatarname; + } + return String.Empty; + } + + public bool ObjectGiveMoney(LLUUID objectID, LLUUID fromID, LLUUID toID, int amount) + { + string description=String.Format("Object {0} pays {1}", resolveObjectName(objectID), resolveAgentName(toID)); + + bool give_result = doMoneyTransfer(fromID, toID, amount, 2, description); + + if (m_MoneyAddress.Length == 0) + BalanceUpdate(fromID, toID, give_result, description); + + return give_result; + + + } + private void BalanceUpdate(LLUUID senderID, LLUUID receiverID, bool transactionresult, string description) + { + IClientAPI sender = LocateClientObject(senderID); + IClientAPI receiver = LocateClientObject(receiverID); + + if (senderID != receiverID) + { + if (sender != null) + { + sender.SendMoneyBalance(LLUUID.Random(), transactionresult, Helpers.StringToField(description), GetFundsForAgentID(senderID)); + } + + if (receiver != null) + { + receiver.SendMoneyBalance(LLUUID.Random(), transactionresult, Helpers.StringToField(description), GetFundsForAgentID(receiverID)); + } + } + } + + /// + /// Informs the Money Grid Server of a transfer. + /// + /// + /// + /// + /// + public bool TransferMoneyonMoneyServer(LLUUID sourceId, LLUUID destId, int amount, int transactiontype, string description) + { + int aggregatePermInventory = 0; + int aggregatePermNextOwner = 0; + int flags = 0; + bool rvalue = false; + + IClientAPI cli = LocateClientObject(sourceId); + if (cli != null) + { + + Scene userScene = null; + lock (m_rootAgents) + { + userScene = GetSceneByUUID(m_rootAgents[sourceId]); + } + if (userScene != null) + { + Hashtable ht = new Hashtable(); + ht["agentId"] = sourceId.ToString(); + ht["secureSessionId"] = cli.SecureSessionId.ToString(); + ht["regionId"] = userScene.RegionInfo.originRegionID.ToString(); + ht["secret"] = userScene.RegionInfo.regionSecret; + ht["currencySecret"] = " "; + ht["destId"] = destId.ToString(); + ht["cash"] = amount; + ht["aggregatePermInventory"] = aggregatePermInventory; + ht["aggregatePermNextOwner"] = aggregatePermNextOwner; + ht["flags"] = flags; + ht["transactionType"] = transactiontype; + ht["description"] = description; + + Hashtable hresult = genericCurrencyXMLRPCRequest(ht, "regionMoveMoney"); + + if ((bool)hresult["success"] == true) + { + int funds1 = 0; + int funds2 = 0; + try + { + funds1 = (Int32)hresult["funds"]; + } + catch(InvalidCastException) + { + funds1 = 0; + } + SetLocalFundsForAgentID(sourceId, funds1); + if (m_KnownClientFunds.ContainsKey(destId)) + { + try + { + funds2 = (Int32)hresult["funds2"]; + } + catch (InvalidCastException) + { + funds2 = 0; + } + SetLocalFundsForAgentID(destId, funds2); + } + + + rvalue = true; + } + else + { + cli.SendAgentAlertMessage((string)hresult["errorMessage"], true); + } + + } + } + else + { + m_log.ErrorFormat("[MONEY]: Client {0} not found", sourceId.ToString()); + } + + return rvalue; + + } + + public int GetRemoteBalance(LLUUID agentId) + { + int funds = 0; + + IClientAPI aClient = LocateClientObject(agentId); + if (aClient != null) + { + Scene s = LocateSceneClientIn(agentId); + if (s != null) + { + if (m_MoneyAddress.Length > 0) + { + Hashtable hbinfo = GetBalanceForUserFromMoneyServer(aClient.AgentId, aClient.SecureSessionId, s.RegionInfo.originRegionID.ToString(), s.RegionInfo.regionSecret); + if ((bool)hbinfo["success"] == true) + { + try + { + funds = (Int32)hbinfo["funds"]; + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + m_log.ErrorFormat("[MONEY]: While getting the Currency for user {0}, the return funds overflowed.", agentId); + aClient.SendAlertMessage("Unable to get your money balance, money operations will be unavailable"); + } + catch (InvalidCastException) + { + funds = 0; + } + + } + else + { + m_log.WarnFormat("[MONEY]: Getting Money for user {0} failed with the following message:{1}", agentId, (string)hbinfo["errorMessage"]); + aClient.SendAlertMessage((string)hbinfo["errorMessage"]); + } + } + + SetLocalFundsForAgentID(agentId, funds); + SendMoneyBalance(aClient, agentId, aClient.SessionId, LLUUID.Zero); + } + else + { + m_log.Debug("[MONEY]: Got balance request update for agent that is here, but couldn't find which scene."); + } + } + else + { + m_log.Debug("[MONEY]: Got balance request update for agent that isn't here."); + } + return funds; + } + + public XmlRpcResponse GridMoneyUpdate(XmlRpcRequest request) + { + m_log.Debug("[MONEY]: Dynamic balance update called."); + Hashtable requestData = (Hashtable)request.Params[0]; + + if (requestData.ContainsKey("agentId")) + { + LLUUID agentId = LLUUID.Zero; + + Helpers.TryParse((string)requestData["agentId"], out agentId); + if (agentId != LLUUID.Zero) + { + GetRemoteBalance(agentId); + + } + else + { + m_log.Debug("[MONEY]: invalid agentId specified, dropping."); + } + } + else + { + m_log.Debug("[MONEY]: no agentId specified, dropping."); + } + XmlRpcResponse r = new XmlRpcResponse(); + Hashtable rparms = new Hashtable(); + rparms["success"] = true; + + r.Value = rparms; + return r; + } + + /// + /// XMLRPC handler to send alert message and sound to client + /// + public XmlRpcResponse UserAlert(XmlRpcRequest request) + { + XmlRpcResponse ret = new XmlRpcResponse(); + Hashtable retparam = new Hashtable(); + Hashtable requestData = (Hashtable)request.Params[0]; + + LLUUID agentId = LLUUID.Zero; + LLUUID soundId = LLUUID.Zero; + + Helpers.TryParse((string)requestData["agentId"], out agentId); + Helpers.TryParse((string)requestData["soundId"], out soundId); + string text=(string)requestData["text"]; + string secret=(string)requestData["secret"]; + + Scene userScene = GetRandomScene(); + if(userScene.RegionInfo.regionSecret.ToString() == secret) + { + IClientAPI client = LocateClientObject(agentId); + + if (client != null) + { + if(soundId != LLUUID.Zero) + client.SendPlayAttachedSound(soundId, LLUUID.Zero, LLUUID.Zero, 1.0f, 0); + client.SendBlueBoxMessage(LLUUID.Zero, LLUUID.Zero, "", text); + retparam.Add("success", true); + } + else + { + retparam.Add("success", false); + } + } + else + { + retparam.Add("success", false); + } + ret.Value = retparam; + + return ret; + } + + + # region Standalone box enablers only + + public XmlRpcResponse quote_func(XmlRpcRequest request) + { + Hashtable requestData = (Hashtable)request.Params[0]; + LLUUID agentId = LLUUID.Zero; + int amount = 0; + Hashtable quoteResponse = new Hashtable(); + XmlRpcResponse returnval = new XmlRpcResponse(); + + if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) + { + Helpers.TryParse((string)requestData["agentId"], out agentId); + try + { + amount = (Int32)requestData["currencyBuy"]; + } + catch (InvalidCastException) + { + + } + Hashtable currencyResponse = new Hashtable(); + currencyResponse.Add("estimatedCost", 0); + currencyResponse.Add("currencyBuy", amount); + + quoteResponse.Add("success", true); + quoteResponse.Add("currency", currencyResponse); + quoteResponse.Add("confirm", "asdfad9fj39ma9fj"); + + returnval.Value = quoteResponse; + return returnval; + } + + + + quoteResponse.Add("success", false); + quoteResponse.Add("errorMessage", "Invalid parameters passed to the quote box"); + quoteResponse.Add("errorURI", "http://www.opensimulator.org/wiki"); + returnval.Value = quoteResponse; + return returnval; + } + public XmlRpcResponse buy_func(XmlRpcRequest request) + { + + Hashtable requestData = (Hashtable)request.Params[0]; + LLUUID agentId = LLUUID.Zero; + int amount = 0; + if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) + { + Helpers.TryParse((string)requestData["agentId"], out agentId); + try + { + amount = (Int32)requestData["currencyBuy"]; + } + catch (InvalidCastException) + { + + } + if (agentId != LLUUID.Zero) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(agentId)) + { + m_KnownClientFunds[agentId] += amount; + } + else + { + m_KnownClientFunds.Add(agentId, amount); + } + } + IClientAPI client = LocateClientObject(agentId); + if (client != null) + { + SendMoneyBalance(client, agentId, client.SessionId, LLUUID.Zero); + } + } + } + XmlRpcResponse returnval = new XmlRpcResponse(); + Hashtable returnresp = new Hashtable(); + returnresp.Add("success", true); + returnval.Value = returnresp; + return returnval; + } + + public XmlRpcResponse preflightBuyLandPrep_func(XmlRpcRequest request) + { + XmlRpcResponse ret = new XmlRpcResponse(); + Hashtable retparam = new Hashtable(); + Hashtable membershiplevels = new Hashtable(); + ArrayList levels = new ArrayList(); + Hashtable level = new Hashtable(); + level.Add("id", "00000000-0000-0000-0000-000000000000"); + level.Add("description", "some level"); + levels.Add(level); + //membershiplevels.Add("levels",levels); + + Hashtable landuse = new Hashtable(); + landuse.Add("upgrade", false); + landuse.Add("action", "http://invaliddomaininvalid.com/"); + + Hashtable currency = new Hashtable(); + currency.Add("estimatedCost", 0); + + Hashtable membership = new Hashtable(); + membershiplevels.Add("upgrade", false); + membershiplevels.Add("action", "http://invaliddomaininvalid.com/"); + membershiplevels.Add("levels", membershiplevels); + + retparam.Add("success", true); + retparam.Add("currency", currency); + retparam.Add("membership", membership); + retparam.Add("landuse", landuse); + retparam.Add("confirm", "asdfajsdkfjasdkfjalsdfjasdf"); + + ret.Value = retparam; + + return ret; + + } + public XmlRpcResponse landBuy_func(XmlRpcRequest request) + { + XmlRpcResponse ret = new XmlRpcResponse(); + Hashtable retparam = new Hashtable(); + Hashtable requestData = (Hashtable)request.Params[0]; + + LLUUID agentId = LLUUID.Zero; + int amount = 0; + if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) + { + Helpers.TryParse((string)requestData["agentId"], out agentId); + try + { + amount = (Int32)requestData["currencyBuy"]; + } + catch (InvalidCastException) + { + + } + if (agentId != LLUUID.Zero) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(agentId)) + { + m_KnownClientFunds[agentId] += amount; + } + else + { + m_KnownClientFunds.Add(agentId, amount); + } + } + IClientAPI client = LocateClientObject(agentId); + if (client != null) + { + SendMoneyBalance(client, agentId, client.SessionId, LLUUID.Zero); + } + } + } + retparam.Add("success", true); + ret.Value = retparam; + + return ret; + + } + #endregion + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "BetaGridLikeMoneyModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + } + + public enum TransactionType : int + { + SystemGenerated=0, + RegionMoneyRequest=1, + Gift=2, + Purchase=3 + + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs new file mode 100644 index 0000000..3b0cc4c --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Friends/FriendsModule.cs @@ -0,0 +1,504 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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.Reflection; +using libsecondlife; +using libsecondlife.Packets; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.Friends +{ + public class FriendsModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private List m_scene = new List(); + + Dictionary m_rootAgents = new Dictionary(); + + Dictionary m_pendingFriendRequests = new Dictionary(); + + Dictionary> FriendLists = new Dictionary>(); + + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_scene) + { + if (m_scene.Count == 0) + { + scene.AddXmlRPCHandler("presence_update", processPresenceUpdate); + } + + if (!m_scene.Contains(scene)) + m_scene.Add(scene); + } + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnGridInstantMessageToFriendsModule += OnGridInstantMessage; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnClientClosed += ClientLoggedOut; + } + public XmlRpcResponse processPresenceUpdate(XmlRpcRequest req) + { + m_log.Info("[FRIENDS]: Got Notification about a user! OMG"); + return new XmlRpcResponse(); + } + private void OnNewClient(IClientAPI client) + { + // All friends establishment protocol goes over instant message + // There's no way to send a message from the sim + // to a user to 'add a friend' without causing dialog box spam + // + // The base set of friends are added when the user signs on in their XMLRPC response + // Generated by LoginService. The friends are retreived from the database by the UserManager + + // Subscribe to instant messages + + client.OnInstantMessage += OnInstantMessage; + client.OnApproveFriendRequest += OnApprovedFriendRequest; + client.OnDenyFriendRequest += OnDenyFriendRequest; + client.OnTerminateFriendship += OnTerminateFriendship; + + List fl = new List(); + + //bool addFLback = false; + + lock (FriendLists) + { + if (FriendLists.ContainsKey(client.AgentId)) + { + fl = FriendLists[client.AgentId]; + } + else + { + fl = m_scene[0].GetFriendList(client.AgentId); + + //lock (FriendLists) + //{ + if (!FriendLists.ContainsKey(client.AgentId)) + FriendLists.Add(client.AgentId, fl); + //} + } + } + + List UpdateUsers = new List(); + + foreach (FriendListItem f in fl) + { + if (m_rootAgents.ContainsKey(f.Friend)) + { + if (f.onlinestatus == false) + { + UpdateUsers.Add(f.Friend); + f.onlinestatus = true; + } + } + } + foreach (LLUUID user in UpdateUsers) + { + ScenePresence av = GetPresenceFromAgentID(user); + if (av != null) + { + List usrfl = new List(); + + lock (FriendLists) + { + usrfl = FriendLists[user]; + } + + lock (usrfl) + { + foreach (FriendListItem fli in usrfl) + { + if (fli.Friend == client.AgentId) + { + fli.onlinestatus = true; + OnlineNotificationPacket onp = new OnlineNotificationPacket(); + OnlineNotificationPacket.AgentBlockBlock[] onpb = new OnlineNotificationPacket.AgentBlockBlock[1]; + OnlineNotificationPacket.AgentBlockBlock onpbl = new OnlineNotificationPacket.AgentBlockBlock(); + onpbl.AgentID = client.AgentId; + onpb[0] = onpbl; + onp.AgentBlock = onpb; + av.ControllingClient.OutPacket(onp, ThrottleOutPacketType.Task); + } + } + } + } + } + + if (UpdateUsers.Count > 0) + { + OnlineNotificationPacket onp = new OnlineNotificationPacket(); + OnlineNotificationPacket.AgentBlockBlock[] onpb = new OnlineNotificationPacket.AgentBlockBlock[UpdateUsers.Count]; + for (int i = 0; i < UpdateUsers.Count; i++) + { + OnlineNotificationPacket.AgentBlockBlock onpbl = new OnlineNotificationPacket.AgentBlockBlock(); + onpbl.AgentID = UpdateUsers[i]; + onpb[i] = onpbl; + } + onp.AgentBlock = onpb; + client.OutPacket(onp, ThrottleOutPacketType.Task); + } + + + + + } + + private void ClientLoggedOut(LLUUID AgentId) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(AgentId)) + { + m_rootAgents.Remove(AgentId); + m_log.Info("[FRIEND]: Removing " + AgentId + ". Agent logged out."); + } + } + List lfli = new List(); + lock (FriendLists) + { + if (FriendLists.ContainsKey(AgentId)) + { + lfli = FriendLists[AgentId]; + } + } + List updateUsers = new List(); + foreach (FriendListItem fli in lfli) + { + if (fli.onlinestatus == true) + { + updateUsers.Add(fli.Friend); + } + } + lock (updateUsers) + { + for (int i = 0; i < updateUsers.Count; i++) + { + List flfli = new List(); + try + { + + lock (FriendLists) + { + if (FriendLists.ContainsKey(updateUsers[i])) + flfli = FriendLists[updateUsers[i]]; + } + } + catch (IndexOutOfRangeException) + { + // Ignore the index out of range exception. + // This causes friend lists to get out of sync slightly.. however + // prevents a sim crash. + m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); + } + + for (int j = 0; j < flfli.Count; j++) + { + try + { + if (flfli[i].Friend == AgentId) + { + flfli[i].onlinestatus = false; + } + + } + + catch (IndexOutOfRangeException) + { + // Ignore the index out of range exception. + // This causes friend lists to get out of sync slightly.. however + // prevents a sim crash. + m_log.Info("[FRIEND]: Unable to enumerate last friendlist user. User logged off"); + } + } + + } + + for (int i = 0; i < updateUsers.Count; i++) + { + ScenePresence av = GetPresenceFromAgentID(updateUsers[i]); + if (av != null) + { + + OfflineNotificationPacket onp = new OfflineNotificationPacket(); + OfflineNotificationPacket.AgentBlockBlock[] onpb = new OfflineNotificationPacket.AgentBlockBlock[1]; + OfflineNotificationPacket.AgentBlockBlock onpbl = new OfflineNotificationPacket.AgentBlockBlock(); + onpbl.AgentID = AgentId; + onpb[0] = onpbl; + onp.AgentBlock = onpb; + av.ControllingClient.OutPacket(onp, ThrottleOutPacketType.Task); + } + } + } + lock (FriendLists) + { + FriendLists.Remove(AgentId); + } + + } + + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, LLUUID regionID) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (avatar.RegionHandle != m_rootAgents[avatar.UUID]) + { + m_rootAgents[avatar.UUID] = avatar.RegionHandle; + m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + if (avatar.JID.Length > 0) + { + JId avatarID = new JId(avatar.JID); + // REST Post XMPP Stanzas! + + } + // Claim User! my user! Mine mine mine! + } + } + else + { + m_rootAgents.Add(avatar.UUID, avatar.RegionHandle); + m_log.Info("[FRIEND]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + } + } + //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); + } + private void MakeChildAgent(ScenePresence avatar) + { + + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) + { + m_rootAgents.Remove(avatar.UUID); + m_log.Info("[FRIEND]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent"); + } + + } + } + + } + #region FriendRequestHandling + private void OnInstantMessage(IClientAPI client,LLUUID fromAgentID, + LLUUID fromAgentSession, LLUUID toAgentID, + LLUUID imSessionID, uint timestamp, string fromAgentName, + string message, byte dialog, bool fromGroup, byte offline, + uint ParentEstateID, LLVector3 Position, LLUUID RegionID, + byte[] binaryBucket) + { + // Friend Requests go by Instant Message.. using the dialog param + // https://wiki.secondlife.com/wiki/ImprovedInstantMessage + + // 38 == Offer friendship + if (dialog == (byte)38) + { + LLUUID friendTransactionID = LLUUID.Random(); + + m_pendingFriendRequests.Add(friendTransactionID, fromAgentID); + + m_log.Info("[FRIEND]: 38 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + message); + GridInstantMessage msg = new GridInstantMessage(); + msg.fromAgentID = fromAgentID.UUID; + msg.fromAgentSession = fromAgentSession.UUID; + msg.toAgentID = toAgentID.UUID; + msg.imSessionID = friendTransactionID.UUID; // This is the item we're mucking with here + m_log.Info("[FRIEND]: Filling Session: " + msg.imSessionID.ToString()); + msg.timestamp = timestamp; + if (client != null) + { + msg.fromAgentName = client.FirstName + " " + client.LastName;// fromAgentName; + } + else + { + msg.fromAgentName = "(hippos)";// Added for posterity. This means that we can't figure out who sent it + } + msg.message = message; + msg.dialog = dialog; + msg.fromGroup = fromGroup; + msg.offline = offline; + msg.ParentEstateID = ParentEstateID; + msg.Position = new sLLVector3(Position); + msg.RegionID = RegionID.UUID; + msg.binaryBucket = binaryBucket; + // We don't really care which scene we pipe it through. + m_scene[0].TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); + } + + // 39 == Accept Friendship + if (dialog == (byte)39) + { + m_log.Info("[FRIEND]: 39 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + message); + } + + // 40 == Decline Friendship + if (dialog == (byte)40) + { + m_log.Info("[FRIEND]: 40 - From:" + fromAgentID.ToString() + " To: " + toAgentID.ToString() + " Session:" + imSessionID.ToString() + " Message:" + message); + } + } + + private void OnApprovedFriendRequest(IClientAPI client, LLUUID agentID, LLUUID transactionID, List callingCardFolders) + { + if (m_pendingFriendRequests.ContainsKey(transactionID)) + { + // Found Pending Friend Request with that Transaction.. + Scene SceneAgentIn = m_scene[0]; + + // Found Pending Friend Request with that Transaction.. + ScenePresence agentpresence = GetPresenceFromAgentID(agentID); + if (agentpresence != null) + { + SceneAgentIn = agentpresence.Scene; + } + + // Compose response to other agent. + GridInstantMessage msg = new GridInstantMessage(); + msg.toAgentID = m_pendingFriendRequests[transactionID].UUID; + msg.fromAgentID = agentID.UUID; + msg.fromAgentName = client.FirstName + " " + client.LastName; + msg.fromAgentSession = client.SessionId.UUID; + msg.fromGroup = false; + msg.imSessionID = transactionID.UUID; + msg.message = agentID.UUID.ToString(); + msg.ParentEstateID = 0; + msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + msg.RegionID = SceneAgentIn.RegionInfo.RegionID.UUID; + msg.dialog = (byte)39;// Approved friend request + msg.Position = new sLLVector3(); + msg.offline = (byte)0; + msg.binaryBucket = new byte[0]; + // We don't really care which scene we pipe it through, it goes to the shared IM Module and/or the database + + SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); + SceneAgentIn.StoreAddFriendship(m_pendingFriendRequests[transactionID], agentID, (uint)1); + m_pendingFriendRequests.Remove(transactionID); + + // TODO: Inform agent that the friend is online + } + } + + private void OnDenyFriendRequest(IClientAPI client, LLUUID agentID, LLUUID transactionID, List callingCardFolders) + { + if (m_pendingFriendRequests.ContainsKey(transactionID)) + { + Scene SceneAgentIn = m_scene[0]; + + // Found Pending Friend Request with that Transaction.. + ScenePresence agentpresence = GetPresenceFromAgentID(agentID); + if (agentpresence != null) + { + SceneAgentIn = agentpresence.Scene; + } + // Compose response to other agent. + GridInstantMessage msg = new GridInstantMessage(); + msg.toAgentID = m_pendingFriendRequests[transactionID].UUID; + msg.fromAgentID = agentID.UUID; + msg.fromAgentName = client.FirstName + " " + client.LastName; + msg.fromAgentSession = client.SessionId.UUID; + msg.fromGroup = false; + msg.imSessionID = transactionID.UUID; + msg.message = agentID.UUID.ToString(); + msg.ParentEstateID = 0; + msg.timestamp = (uint)Util.UnixTimeSinceEpoch(); + msg.RegionID = SceneAgentIn.RegionInfo.RegionID.UUID; + msg.dialog = (byte)40;// Deny friend request + msg.Position = new sLLVector3(); + msg.offline = (byte)0; + msg.binaryBucket = new byte[0]; + SceneAgentIn.TriggerGridInstantMessage(msg, InstantMessageReceiver.IMModule); + m_pendingFriendRequests.Remove(transactionID); + } + } + + private void OnTerminateFriendship(IClientAPI client, LLUUID agent, LLUUID exfriendID) + { + m_scene[0].StoreRemoveFriendship(agent, exfriendID); + // TODO: Inform the client that the ExFriend is offline + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // Trigger the above event handler + OnInstantMessage(null,new LLUUID(msg.fromAgentID), new LLUUID(msg.fromAgentSession), + new LLUUID(msg.toAgentID), new LLUUID(msg.imSessionID), msg.timestamp, msg.fromAgentName, + msg.message, msg.dialog, msg.fromGroup, msg.offline, msg.ParentEstateID, + new LLVector3(msg.Position.x, msg.Position.y, msg.Position.z), new LLUUID(msg.RegionID), + msg.binaryBucket); + } + #endregion + private ScenePresence GetPresenceFromAgentID(LLUUID AgentID) + { + ScenePresence returnAgent = null; + lock (m_scene) + { + ScenePresence queryagent = null; + for (int i = 0; i < m_scene.Count; i++) + { + queryagent = m_scene[i].GetScenePresence(AgentID); + if (queryagent != null) + { + if (!queryagent.IsChildAgent) + { + returnAgent = queryagent; + break; + } + } + } + } + return returnAgent; + + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "FriendsModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Groups/GroupsModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Groups/GroupsModule.cs new file mode 100644 index 0000000..4b28ad7 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Groups/GroupsModule.cs @@ -0,0 +1,278 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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.Reflection; +using libsecondlife; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.Groups +{ + public class GroupsModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private List m_scene = new List(); + private Dictionary m_iclientmap = new Dictionary(); + private Dictionary m_groupmap = new Dictionary(); + private Dictionary m_grouplistmap = new Dictionary(); + + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_scene) + { + m_scene.Add(scene); + } + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnClientClosed += OnClientClosed; + scene.EventManager.OnGridInstantMessageToGroupsModule += OnGridInstantMessage; + //scene.EventManager. + } + + private void OnNewClient(IClientAPI client) + { + // All friends establishment protocol goes over instant message + // There's no way to send a message from the sim + // to a user to 'add a friend' without causing dialog box spam + // + // The base set of friends are added when the user signs on in their XMLRPC response + // Generated by LoginService. The friends are retreived from the database by the UserManager + + // Subscribe to instant messages + client.OnInstantMessage += OnInstantMessage; + client.OnAgentDataUpdateRequest += OnAgentDataUpdateRequest; + lock (m_iclientmap) + { + if (!m_iclientmap.ContainsKey(client.AgentId)) + { + m_iclientmap.Add(client.AgentId, client); + } + } + GroupData OpenSimulatorGroup = new GroupData(); + OpenSimulatorGroup.ActiveGroupTitle = "OpenSimulator Tester"; + OpenSimulatorGroup.GroupID = new LLUUID("00000000-68f9-1111-024e-222222111120"); + OpenSimulatorGroup.GroupMembers.Add(client.AgentId); + OpenSimulatorGroup.groupName = "OpenSimulator Testing"; + OpenSimulatorGroup.ActiveGroupPowers = GroupPowers.LandAllowSetHome; + OpenSimulatorGroup.GroupTitles.Add("OpenSimulator Tester"); + lock (m_groupmap) + { + if (!m_groupmap.ContainsKey(client.AgentId)) + { + m_groupmap.Add(client.AgentId, OpenSimulatorGroup); + } + } + GroupList testGroupList = new GroupList(); + testGroupList.m_GroupList.Add(new LLUUID("00000000-68f9-1111-024e-222222111120")); + + lock (m_grouplistmap) + { + if (!m_grouplistmap.ContainsKey(client.AgentId)) + { + m_grouplistmap.Add(client.AgentId, testGroupList); + } + } + m_log.Info("[GROUP]: Adding " + client.FirstName + " " + client.LastName + " to OpenSimulator Tester group"); + } + + private void OnAgentDataUpdateRequest(IClientAPI remoteClient, LLUUID AgentID, LLUUID SessionID) + { + string firstname = remoteClient.FirstName; + string lastname = remoteClient.LastName; + + LLUUID ActiveGroupID = LLUUID.Zero; + uint ActiveGroupPowers = 0; + string ActiveGroupName = ""; + string ActiveGroupTitle = ""; + + bool foundUser = false; + + lock (m_iclientmap) + { + if (m_iclientmap.ContainsKey(remoteClient.AgentId)) + { + foundUser = true; + } + } + if (foundUser) + { + lock (m_groupmap) + { + if (m_groupmap.ContainsKey(remoteClient.AgentId)) + { + GroupData grp = m_groupmap[remoteClient.AgentId]; + if (grp != null) + { + ActiveGroupID = grp.GroupID; + ActiveGroupName = grp.groupName; + ActiveGroupPowers = grp.groupPowers; + ActiveGroupTitle = grp.ActiveGroupTitle; + } + + //remoteClient.SendAgentDataUpdate(AgentID, ActiveGroupID, firstname, lastname, ActiveGroupPowers, ActiveGroupName, ActiveGroupTitle); + + } + } + } + + } + + private void OnInstantMessage(IClientAPI client, LLUUID fromAgentID, + LLUUID fromAgentSession, LLUUID toAgentID, + LLUUID imSessionID, uint timestamp, string fromAgentName, + string message, byte dialog, bool fromGroup, byte offline, + uint ParentEstateID, LLVector3 Position, LLUUID RegionID, + byte[] binaryBucket) + { + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // Trigger the above event handler + OnInstantMessage(null, new LLUUID(msg.fromAgentID), new LLUUID(msg.fromAgentSession), + new LLUUID(msg.toAgentID), new LLUUID(msg.imSessionID), msg.timestamp, msg.fromAgentName, + msg.message, msg.dialog, msg.fromGroup, msg.offline, msg.ParentEstateID, + new LLVector3(msg.Position.x, msg.Position.y, msg.Position.z), new LLUUID(msg.RegionID), + msg.binaryBucket); + } + + private void OnClientClosed(LLUUID agentID) + { + lock (m_iclientmap) + { + if (m_iclientmap.ContainsKey(agentID)) + { + IClientAPI cli = m_iclientmap[agentID]; + if (cli != null) + { + m_log.Info("[GROUP]: Removing all reference to groups for " + cli.FirstName + " " + cli.LastName); + } + else + { + m_log.Info("[GROUP]: Removing all reference to groups for " + agentID.ToString()); + } + m_iclientmap.Remove(agentID); + } + } + + lock (m_groupmap) + { + if (m_groupmap.ContainsKey(agentID)) + { + m_groupmap.Remove(agentID); + } + } + + lock (m_grouplistmap) + { + if (m_grouplistmap.ContainsKey(agentID)) + { + m_grouplistmap.Remove(agentID); + } + } + GC.Collect(); + } + + public void PostInitialise() + { + } + + public void Close() + { + m_log.Info("[GROUP]: Shutting down group module."); + lock (m_iclientmap) + { + m_iclientmap.Clear(); + } + + lock (m_groupmap) + { + m_groupmap.Clear(); + } + + lock (m_grouplistmap) + { + m_grouplistmap.Clear(); + } + GC.Collect(); + } + + public string Name + { + get { return "GroupsModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + } + + public class GroupData + { + public LLUUID GroupID; + public string groupName; + public string ActiveGroupTitle; + public List GroupTitles; + public List GroupMembers; + public uint groupPowers = (uint)(GroupPowers.LandAllowLandmark | GroupPowers.LandAllowSetHome); + + public GroupPowers ActiveGroupPowers + { + set + { + groupPowers = (uint) value; + } + get + { + return (GroupPowers)groupPowers; + } + } + + public GroupData() + { + GroupTitles = new List(); + GroupMembers = new List(); + } + + } + + public class GroupList + { + public List m_GroupList; + public GroupList() + { + m_GroupList = new List(); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs new file mode 100644 index 0000000..1b82837 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/InstantMessage/InstantMessageModule.cs @@ -0,0 +1,157 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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.Collections.Generic; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.InstantMessage +{ + public class InstantMessageModule : IRegionModule + { + private readonly List m_scenes = new List(); + + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_scenes) + { + if (m_scenes.Count == 0) + { + //scene.AddXmlRPCHandler("avatar_location_update", processPresenceUpdate); + } + + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnGridInstantMessageToIMModule += OnGridInstantMessage; + } + } + } + + private void OnNewClient(IClientAPI client) + { + client.OnInstantMessage += OnInstantMessage; + } + + private void OnInstantMessage(IClientAPI client,LLUUID fromAgentID, + LLUUID fromAgentSession, LLUUID toAgentID, + LLUUID imSessionID, uint timestamp, string fromAgentName, + string message, byte dialog, bool fromGroup, byte offline, + uint ParentEstateID, LLVector3 Position, LLUUID RegionID, + byte[] binaryBucket) + { + bool dialogHandledElsewhere + = ((dialog == 38) || (dialog == 39) || (dialog == 40) + || dialog == (byte)InstantMessageDialog.InventoryOffered + || dialog == (byte)InstantMessageDialog.InventoryAccepted + || dialog == (byte)InstantMessageDialog.InventoryDeclined); + + // IM dialogs need to be pre-processed and have their sessionID filled by the server + // so the sim can match the transaction on the return packet. + + // Don't send a Friend Dialog IM with a LLUUID.Zero session. + if (!(dialogHandledElsewhere && imSessionID == LLUUID.Zero)) + { + // Try root avatar only first + foreach (Scene scene in m_scenes) + { + if (scene.Entities.ContainsKey(toAgentID) && scene.Entities[toAgentID] is ScenePresence) + { + // Local message + ScenePresence user = (ScenePresence)scene.Entities[toAgentID]; + if (!user.IsChildAgent) + { + user.ControllingClient.SendInstantMessage(fromAgentID, fromAgentSession, message, + toAgentID, imSessionID, fromAgentName, dialog, + timestamp); + // Message sent + return; + } + } + } + + // try child avatar second + foreach (Scene scene in m_scenes) + { + if (scene.Entities.ContainsKey(toAgentID) && scene.Entities[toAgentID] is ScenePresence) + { + // Local message + ScenePresence user = (ScenePresence)scene.Entities[toAgentID]; + + user.ControllingClient.SendInstantMessage(fromAgentID, fromAgentSession, message, + toAgentID, imSessionID, fromAgentName, dialog, + timestamp); + // Message sent + return; + + } + } + + } + + + // Still here, try send via Grid + // TODO + } + + // Trusty OSG1 called method. This method also gets called from the FriendsModule + // Turns out the sim has to send an instant message to the user to get it to show an accepted friend. + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // Trigger the above event handler + OnInstantMessage(null,new LLUUID(msg.fromAgentID), new LLUUID(msg.fromAgentSession), + new LLUUID(msg.toAgentID), new LLUUID(msg.imSessionID), msg.timestamp, msg.fromAgentName, + msg.message, msg.dialog, msg.fromGroup, msg.offline, msg.ParentEstateID, + new LLVector3(msg.Position.x,msg.Position.y,msg.Position.z), new LLUUID(msg.RegionID), + msg.binaryBucket); + + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "InstantMessageModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Inventory/InventoryModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Inventory/InventoryModule.cs new file mode 100644 index 0000000..42c6238 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Inventory/InventoryModule.cs @@ -0,0 +1,216 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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.Collections.Generic; +using System.Reflection; +using libsecondlife; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.Inventory +{ + public class InventoryModule : IRegionModule + { + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + + /// + /// We need to keep track of the pending item offers between clients since the itemId offered only + /// occurs in the initial offer message, not the accept message. So this dictionary links + /// IM Session Ids to ItemIds + /// + private IDictionary m_pendingOffers = new Dictionary(); + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + scene.EventManager.OnNewClient += OnNewClient; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "InventoryModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + private void OnNewClient(IClientAPI client) + { + // Inventory giving is conducted via instant message + client.OnInstantMessage += OnInstantMessage; + } + + private void OnInstantMessage(IClientAPI client, LLUUID fromAgentID, + LLUUID fromAgentSession, LLUUID toAgentID, + LLUUID imSessionID, uint timestamp, string fromAgentName, + string message, byte dialog, bool fromGroup, byte offline, + uint ParentEstateID, LLVector3 Position, LLUUID RegionID, + byte[] binaryBucket) + { + if (dialog == (byte)InstantMessageDialog.InventoryOffered) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Routing inventory offering message from {0}, {1} to {2}", + client.AgentId, client.Name, toAgentID); + + if (m_scene.Entities.ContainsKey(toAgentID) && m_scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = (ScenePresence)m_scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + //byte[] rawId = new byte[16]; + + // First byte of the array is probably the item type + // Next 16 bytes are the UUID + //Array.Copy(binaryBucket, 1, rawId, 0, 16); + + //LLUUID itemId = new LLUUID(new Guid(rawId)); + LLUUID itemId = new LLUUID(binaryBucket, 1); + + m_log.DebugFormat( + "[AGENT INVENTORY]: ItemId for giving is {0}", itemId); + + m_pendingOffers[imSessionID] = itemId; + + user.ControllingClient.SendInstantMessage( + fromAgentID, fromAgentSession, message, toAgentID, imSessionID, fromAgentName, + dialog, timestamp, binaryBucket); + + return; + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Agent {0} targeted for inventory give by {1}, {2} of {3} was a child agent!", + toAgentID, client.AgentId, client.Name, message); + } + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Could not find agent {0} for user {1}, {2} to give {3}", + toAgentID, client.AgentId, client.Name, message); + } + } + else if (dialog == (byte)InstantMessageDialog.InventoryAccepted) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Routing inventory accepted message from {0}, {1} to {2}", + client.AgentId, client.Name, toAgentID); + + if (m_scene.Entities.ContainsKey(toAgentID) && m_scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = (ScenePresence)m_scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + user.ControllingClient.SendInstantMessage( + fromAgentID, fromAgentSession, message, toAgentID, imSessionID, fromAgentName, + dialog, timestamp, binaryBucket); + + if (m_pendingOffers.ContainsKey(imSessionID)) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Accepted item id {0}", m_pendingOffers[imSessionID]); + + // Since the message originates from the accepting client, the toAgentID is + // the agent giving the item. + m_scene.GiveInventoryItem(client, toAgentID, m_pendingOffers[imSessionID]); + + m_pendingOffers.Remove(imSessionID); + } + else + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Could not find an item associated with session id {0} to accept", + imSessionID); + } + + return; + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Agent {0} targeted for inventory give by {1}, {2} of {3} was a child agent!", + toAgentID, client.AgentId, client.Name, message); + } + } + else + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Could not find agent {0} for user {1}, {2} to give {3}", + toAgentID, client.AgentId, client.Name, message); + } + } + else if (dialog == (byte)InstantMessageDialog.InventoryDeclined) + { + if (m_scene.Entities.ContainsKey(toAgentID) && m_scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = (ScenePresence)m_scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + user.ControllingClient.SendInstantMessage( + fromAgentID, fromAgentSession, message, toAgentID, imSessionID, fromAgentName, + dialog, timestamp, binaryBucket); + + if (m_pendingOffers.ContainsKey(imSessionID)) + { + m_log.DebugFormat( + "[AGENT INVENTORY]: Declined item id {0}", m_pendingOffers[imSessionID]); + + m_pendingOffers.Remove(imSessionID); + } + else + { + m_log.ErrorFormat( + "[AGENT INVENTORY]: Could not find an item associated with session id {0} to decline", + imSessionID); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Profiles/AvatarProfilesModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Profiles/AvatarProfilesModule.cs new file mode 100644 index 0000000..f8b14d3 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Profiles/AvatarProfilesModule.cs @@ -0,0 +1,129 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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.Reflection; +using libsecondlife; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; + +namespace OpenSim.Region.Environment.Modules.Avatar.Profiles +{ + public class AvatarProfilesModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene; + + public AvatarProfilesModule() + { + } + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + m_scene.EventManager.OnNewClient += NewClient; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "AvatarProfilesModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + public void NewClient(IClientAPI client) + { + client.OnRequestAvatarProperties += RequestAvatarProperty; + client.OnUpdateAvatarProperties += UpdateAvatarProperties; + } + + public void RemoveClient(IClientAPI client) + { + client.OnRequestAvatarProperties -= RequestAvatarProperty; + client.OnUpdateAvatarProperties -= UpdateAvatarProperties; + } + + /// + /// + /// + /// + /// + public void RequestAvatarProperty(IClientAPI remoteClient, LLUUID avatarID) + { + // FIXME: finish adding fields such as url, masking, etc. + LLUUID partner = new LLUUID("11111111-1111-0000-0000-000100bba000"); + UserProfileData profile = m_scene.CommsManager.UserService.GetUserProfile(avatarID); + if (null != profile) + { + remoteClient.SendAvatarProperties(profile.ID, profile.AboutText, + Util.ToDateTime(profile.Created).ToString(), + String.Empty, profile.FirstLifeAboutText, profile.CanDoMask, + profile.FirstLifeImage, profile.Image, String.Empty, partner); + } + else + { + m_log.Debug("[AvatarProfilesModule]: Got null for profile for " + avatarID.ToString()); + } + } + + public void UpdateAvatarProperties(IClientAPI remoteClient, UserProfileData newProfile) + { + UserProfileData Profile = m_scene.CommsManager.UserService.GetUserProfile(newProfile.ID); + + // if it's the profile of the user requesting the update, then we change only a few things. + if (remoteClient.AgentId.CompareTo(Profile.ID) == 0) + { + Profile.Image = newProfile.Image; + Profile.FirstLifeImage = newProfile.FirstLifeImage; + Profile.AboutText = newProfile.AboutText; + Profile.FirstLifeAboutText = newProfile.FirstLifeAboutText; + } + else + { + return; + } + if (m_scene.CommsManager.UserService.UpdateUserProfileProperties(Profile)) + { + RequestAvatarProperty(remoteClient, newProfile.ID); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs new file mode 100644 index 0000000..0d7de78 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs @@ -0,0 +1,285 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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; +using System.Reflection; +using libsecondlife; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Capabilities; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using Caps=OpenSim.Region.Capabilities.Caps; + +namespace OpenSim.Region.Environment.Modules.Avatar.Voice.AsterixVoice +{ + public class AsteriskVoiceModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private IConfig m_config; + private string m_asterisk; + private string m_asterisk_password; + private string m_asterisk_salt; + private int m_asterisk_timeout; + private string m_sipDomain; + private string m_confDomain; + + private static readonly string m_parcelVoiceInfoRequestPath = "0007/"; + private static readonly string m_provisionVoiceAccountRequestPath = "0008/"; + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + m_config = config.Configs["AsteriskVoice"]; + + if (null == m_config) + { + m_log.Info("[ASTERISKVOICE] no config found, plugin disabled"); + return; + } + + if (!m_config.GetBoolean("enabled", false)) + { + m_log.Info("[ASTERISKVOICE] plugin disabled by configuration"); + return; + } + m_log.Info("[ASTERISKVOICE] plugin enabled"); + + try { + m_sipDomain = m_config.GetString("sip_domain", String.Empty); + m_log.InfoFormat("[ASTERISKVOICE] using SIP domain {0}", m_sipDomain); + + m_confDomain = m_config.GetString("conf_domain", String.Empty); + m_log.InfoFormat("[ASTERISKVOICE] using conf domain {0}", m_confDomain); + + m_asterisk = m_config.GetString("asterisk_frontend", String.Empty); + m_asterisk_password = m_config.GetString("asterisk_password", String.Empty); + m_asterisk_timeout = m_config.GetInt("asterisk_timeout", 3000); + m_asterisk_salt = m_config.GetString("asterisk_salt", "Wuffwuff"); + if (String.IsNullOrEmpty(m_asterisk)) throw new Exception("missing asterisk_frontend config parameter"); + if (String.IsNullOrEmpty(m_asterisk_password)) throw new Exception("missing asterisk_password config parameter"); + m_log.InfoFormat("[ASTERISKVOICE] using asterisk front end {0}", m_asterisk); + + scene.EventManager.OnRegisterCaps += OnRegisterCaps; + } + catch (Exception e) + { + m_log.ErrorFormat("[ASTERISKVOICE] plugin initialization failed: {0}", e.Message); + m_log.DebugFormat("[ASTERISKVOICE] plugin initialization failed: {0}", e.ToString()); + return; + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "AsteriskVoiceModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + public void OnRegisterCaps(LLUUID agentID, Caps caps) + { + m_log.DebugFormat("[ASTERISKVOICE] OnRegisterCaps: agentID {0} caps {1}", agentID, caps); + string capsBase = "/CAPS/" + caps.CapsObjectPath; + caps.RegisterHandler("ParcelVoiceInfoRequest", + new RestStreamHandler("POST", capsBase + m_parcelVoiceInfoRequestPath, + delegate(string request, string path, string param) + { + return ParcelVoiceInfoRequest(request, path, param, + agentID, caps); + })); + caps.RegisterHandler("ProvisionVoiceAccountRequest", + new RestStreamHandler("POST", capsBase + m_provisionVoiceAccountRequestPath, + delegate(string request, string path, string param) + { + return ProvisionVoiceAccountRequest(request, path, param, + agentID, caps); + })); + } + + /// + /// Callback for a client request for ParcelVoiceInfo + /// + /// + /// + /// + /// + /// + /// + public string ParcelVoiceInfoRequest(string request, string path, string param, + LLUUID agentID, Caps caps) + { + // we need to do: + // - send channel_uri: as "sip:regionID@m_sipDomain" + try + { + m_log.DebugFormat("[ASTERISKVOICE][PARCELVOICE]: request: {0}, path: {1}, param: {2}", + request, path, param); + + + // setup response to client + Hashtable creds = new Hashtable(); + creds["channel_uri"] = String.Format("sip:{0}@{1}", + m_scene.RegionInfo.RegionID, m_sipDomain); + + string regionName = m_scene.RegionInfo.RegionName; + ScenePresence avatar = m_scene.GetScenePresence(agentID); + if (null == m_scene.LandChannel) throw new Exception("land data not yet available"); + LandData land = m_scene.GetLandData(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + + LLSDParcelVoiceInfoResponse parcelVoiceInfo = + new LLSDParcelVoiceInfoResponse(regionName, land.localID, creds); + + string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo); + + + // update region on asterisk-opensim frontend + Hashtable requestData = new Hashtable(); + requestData["admin_password"] = m_asterisk_password; + requestData["region"] = m_scene.RegionInfo.RegionID.ToString(); + if (!String.IsNullOrEmpty(m_confDomain)) + { + requestData["region"] += String.Format("@{0}", m_confDomain); + } + + ArrayList SendParams = new ArrayList(); + SendParams.Add(requestData); + XmlRpcRequest updateAccountRequest = new XmlRpcRequest("region_update", SendParams); + XmlRpcResponse updateAccountResponse = updateAccountRequest.Send(m_asterisk, m_asterisk_timeout); + Hashtable responseData = (Hashtable)updateAccountResponse.Value; + + if (!responseData.ContainsKey("success")) throw new Exception("region_update call failed"); + + bool success = Convert.ToBoolean((string)responseData["success"]); + if (!success) throw new Exception("region_update failed"); + + + m_log.DebugFormat("[ASTERISKVOICE][PARCELVOICE]: {0}", r); + return r; + } + catch (Exception e) + { + m_log.ErrorFormat("[ASTERISKVOICE][CAPS][PARCELVOICE]: {0}, retry later", e.Message); + m_log.DebugFormat("[ASTERISKVOICE][CAPS][PARCELVOICE]: {0} failed", e.ToString()); + + return "undef"; + } + } + + /// + /// Callback for a client request for Voice Account Details + /// + /// + /// + /// + /// + /// + /// + public string ProvisionVoiceAccountRequest(string request, string path, string param, + LLUUID agentID, Caps caps) + { + // we need to + // - get user data from UserProfileCacheService + // - generate nonce for user voice account password + // - issue XmlRpc request to asterisk opensim front end: + // + user: base 64 encoded user name (otherwise SL + // client is unhappy) + // + password: nonce + // - the XmlRpc call to asteris-opensim was successful: + // send account details back to client + try + { + m_log.DebugFormat("[ASTERISKVOICE][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}", + request, path, param); + + // get user data & prepare voice account response + string voiceUser = "x" + Convert.ToBase64String(agentID.GetBytes()); + voiceUser = voiceUser.Replace('+', '-').Replace('/', '_'); + + CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(agentID); + if (null == userInfo) throw new Exception("cannot get user details"); + + // we generate a nonce everytime + string voicePassword = "$1$" + Util.Md5Hash(DateTime.UtcNow.ToLongTimeString() + m_asterisk_salt); + LLSDVoiceAccountResponse voiceAccountResponse = + new LLSDVoiceAccountResponse(voiceUser, voicePassword); + string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse); + m_log.DebugFormat("[CAPS][PROVISIONVOICE]: {0}", r); + + + // update user account on asterisk frontend + Hashtable requestData = new Hashtable(); + requestData["admin_password"] = m_asterisk_password; + requestData["username"] = voiceUser; + if (!String.IsNullOrEmpty(m_sipDomain)) + { + requestData["username"] += String.Format("@{0}", m_sipDomain); + } + requestData["password"] = voicePassword; + + ArrayList SendParams = new ArrayList(); + SendParams.Add(requestData); + XmlRpcRequest updateAccountRequest = new XmlRpcRequest("account_update", SendParams); + XmlRpcResponse updateAccountResponse = updateAccountRequest.Send(m_asterisk, m_asterisk_timeout); + Hashtable responseData = (Hashtable)updateAccountResponse.Value; + + if (!responseData.ContainsKey("success")) throw new Exception("account_update call failed"); + + bool success = Convert.ToBoolean((string)responseData["success"]); + if (!success) throw new Exception("account_update failed"); + + return r; + } + catch (Exception e) + { + m_log.ErrorFormat("[ASTERISKVOICE][CAPS][PROVISIONVOICE]: {0}, retry later", e.Message); + m_log.DebugFormat("[ASTERISKVOICE][CAPS][PROVISIONVOICE]: {0} failed", e.ToString()); + + return "undef"; + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs new file mode 100644 index 0000000..8b7c3d0 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * 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 OpenSim Project 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 DEVELOPERS ``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 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; +using System.Reflection; +using libsecondlife; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Capabilities; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using Caps=OpenSim.Region.Capabilities.Caps; + +namespace OpenSim.Region.Environment.Modules.Avatar.Voice.SIPVoice +{ + public class SIPVoiceModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private IConfig m_config; + private string m_sipDomain; + + private static readonly string m_parcelVoiceInfoRequestPath = "0007/"; + private static readonly string m_provisionVoiceAccountRequestPath = "0008/"; + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + m_config = config.Configs["Voice"]; + + if (null == m_config || !m_config.GetBoolean("enabled", false)) + { + m_log.Info("[VOICE] plugin disabled"); + return; + } + m_log.Info("[VOICE] plugin enabled"); + + m_sipDomain = m_config.GetString("sip_domain", String.Empty); + if (String.IsNullOrEmpty(m_sipDomain)) + { + m_log.Error("[VOICE] plugin mis-configured: missing sip_domain configuration"); + m_log.Info("[VOICE] plugin disabled"); + return; + } + m_log.InfoFormat("[VOICE] using SIP domain {0}", m_sipDomain); + + scene.EventManager.OnRegisterCaps += OnRegisterCaps; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "VoiceModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + public void OnRegisterCaps(LLUUID agentID, Caps caps) + { + m_log.DebugFormat("[VOICE] OnRegisterCaps: agentID {0} caps {1}", agentID, caps); + string capsBase = "/CAPS/" + caps.CapsObjectPath; + caps.RegisterHandler("ParcelVoiceInfoRequest", + new RestStreamHandler("POST", capsBase + m_parcelVoiceInfoRequestPath, + delegate(string request, string path, string param) + { + return ParcelVoiceInfoRequest(request, path, param, + agentID, caps); + })); + caps.RegisterHandler("ProvisionVoiceAccountRequest", + new RestStreamHandler("POST", capsBase + m_provisionVoiceAccountRequestPath, + delegate(string request, string path, string param) + { + return ProvisionVoiceAccountRequest(request, path, param, + agentID, caps); + })); + } + + /// + /// Callback for a client request for ParcelVoiceInfo + /// + /// + /// + /// + /// + /// + /// + public string ParcelVoiceInfoRequest(string request, string path, string param, + LLUUID agentID, Caps caps) + { + try + { + m_log.DebugFormat("[VOICE][PARCELVOICE]: request: {0}, path: {1}, param: {2}", request, path, param); + + // FIXME: get the creds from region file or from config + Hashtable creds = new Hashtable(); + + creds["channel_uri"] = String.Format("sip:{0}@{1}", agentID, m_sipDomain); + + string regionName = m_scene.RegionInfo.RegionName; + ScenePresence avatar = m_scene.GetScenePresence(agentID); + if (null == m_scene.LandChannel) throw new Exception("land data not yet available"); + LandData land = m_scene.GetLandData(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + + LLSDParcelVoiceInfoResponse parcelVoiceInfo = + new LLSDParcelVoiceInfoResponse(regionName, land.localID, creds); + + string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo); + m_log.DebugFormat("[VOICE][PARCELVOICE]: {0}", r); + + return r; + } + catch (Exception e) + { + m_log.ErrorFormat("[CAPS]: {0}, try again later", e.ToString()); + } + + return null; + } + + /// + /// Callback for a client request for Voice Account Details + /// + /// + /// + /// + /// + /// + /// + public string ProvisionVoiceAccountRequest(string request, string path, string param, + LLUUID agentID, Caps caps) + { + try + { + m_log.DebugFormat("[VOICE][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}", + request, path, param); + + string voiceUser = "x" + Convert.ToBase64String(agentID.GetBytes()); + voiceUser = voiceUser.Replace('+', '-').Replace('/', '_'); + + CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(agentID); + if (null == userInfo) throw new Exception("cannot get user details"); + + LLSDVoiceAccountResponse voiceAccountResponse = + new LLSDVoiceAccountResponse(voiceUser, "$1$" + userInfo.UserProfile.PasswordHash); + string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse); + m_log.DebugFormat("[CAPS][PROVISIONVOICE]: {0}", r); + return r; + } + catch (Exception e) + { + m_log.ErrorFormat("[CAPS][PROVISIONVOICE]: {0}, retry later", e.Message); + } + + return null; + } + } +} \ No newline at end of file -- cgit v1.1