OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
ChatModule.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.Reflection;
31 using log4net;
32 using Nini.Config;
33 using Mono.Addins;
34 using OpenMetaverse;
35 using OpenMetaverse.StructuredData;
36 using OpenSim.Framework;
37 using OpenSim.Region.Framework.Interfaces;
38 using OpenSim.Region.Framework.Scenes;
39 
40 namespace OpenSim.Region.CoreModules.Avatar.Chat
41 {
42  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "ChatModule")]
44  {
45  private static readonly ILog m_log =
46  LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47 
48  private const int DEBUG_CHANNEL = 2147483647;
49 
50  private bool m_enabled = true;
51  private int m_saydistance = 20;
52  private int m_shoutdistance = 100;
53  private int m_whisperdistance = 10;
54  private List<Scene> m_scenes = new List<Scene>();
55  private List<string> FreezeCache = new List<string>();
56  private string m_adminPrefix = "";
57  internal object m_syncy = new object();
58 
59  internal IConfig m_config;
60 
61  #region ISharedRegionModule Members
62  public virtual void Initialise(IConfigSource config)
63  {
64  m_config = config.Configs["Chat"];
65 
66  if (m_config != null)
67  {
68  if (!m_config.GetBoolean("enabled", true))
69  {
70  m_log.Info("[CHAT]: plugin disabled by configuration");
71  m_enabled = false;
72  return;
73  }
74 
75  m_whisperdistance = m_config.GetInt("whisper_distance", m_whisperdistance);
76  m_saydistance = m_config.GetInt("say_distance", m_saydistance);
77  m_shoutdistance = m_config.GetInt("shout_distance", m_shoutdistance);
78  m_adminPrefix = m_config.GetString("admin_prefix", "");
79  }
80  }
81 
82  public virtual void AddRegion(Scene scene)
83  {
84  if (!m_enabled) return;
85 
86  lock (m_syncy)
87  {
88  if (!m_scenes.Contains(scene))
89  {
90  m_scenes.Add(scene);
91  scene.EventManager.OnNewClient += OnNewClient;
92  scene.EventManager.OnChatFromWorld += OnChatFromWorld;
93  scene.EventManager.OnChatBroadcast += OnChatBroadcast;
94  }
95  }
96 
97  m_log.InfoFormat("[CHAT]: Initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName,
98  m_whisperdistance, m_saydistance, m_shoutdistance);
99  }
100 
101  public virtual void RegionLoaded(Scene scene)
102  {
103  if (!m_enabled)
104  return;
105 
106  ISimulatorFeaturesModule featuresModule = scene.RequestModuleInterface<ISimulatorFeaturesModule>();
107 
108  if (featuresModule != null)
109  featuresModule.OnSimulatorFeaturesRequest += OnSimulatorFeaturesRequest;
110 
111  }
112 
113  public virtual void RemoveRegion(Scene scene)
114  {
115  if (!m_enabled) return;
116 
117  lock (m_syncy)
118  {
119  if (m_scenes.Contains(scene))
120  {
121  scene.EventManager.OnNewClient -= OnNewClient;
122  scene.EventManager.OnChatFromWorld -= OnChatFromWorld;
123  scene.EventManager.OnChatBroadcast -= OnChatBroadcast;
124  m_scenes.Remove(scene);
125  }
126  }
127  }
128 
129  public virtual void Close()
130  {
131  }
132 
133  public virtual void PostInitialise()
134  {
135  }
136 
137  public Type ReplaceableInterface
138  {
139  get { return null; }
140  }
141 
142  public virtual string Name
143  {
144  get { return "ChatModule"; }
145  }
146 
147  #endregion
148 
149 
150  public virtual void OnNewClient(IClientAPI client)
151  {
152  client.OnChatFromClient += OnChatFromClient;
153  }
154 
156  {
157  ScenePresence avatar;
158  Scene scene = (Scene)c.Scene;
159  if ((avatar = scene.GetScenePresence(c.Sender.AgentId)) != null)
160  c.Position = avatar.AbsolutePosition;
161 
162  return c;
163  }
164 
165  public virtual void OnChatFromClient(Object sender, OSChatMessage c)
166  {
167  c = FixPositionOfChatMessage(c);
168 
169  // redistribute to interested subscribers
170  Scene scene = (Scene)c.Scene;
171  scene.EventManager.TriggerOnChatFromClient(sender, c);
172 
173  // early return if not on public or debug channel
174  if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return;
175 
176  // sanity check:
177  if (c.Sender == null)
178  {
179  m_log.ErrorFormat("[CHAT]: OnChatFromClient from {0} has empty Sender field!", sender);
180  return;
181  }
182 
183  if (FreezeCache.Contains(c.Sender.AgentId.ToString()))
184  {
185  if (c.Type != ChatTypeEnum.StartTyping || c.Type != ChatTypeEnum.StopTyping)
186  c.Sender.SendAgentAlertMessage("You may not talk as you are frozen.", false);
187  }
188  else
189  {
190  DeliverChatToAvatars(ChatSourceType.Agent, c);
191  }
192  }
193 
194  public virtual void OnChatFromWorld(Object sender, OSChatMessage c)
195  {
196  // early return if not on public or debug channel
197  if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return;
198 
199  DeliverChatToAvatars(ChatSourceType.Object, c);
200  }
201 
202  protected virtual void DeliverChatToAvatars(ChatSourceType sourceType, OSChatMessage c)
203  {
204  string fromName = c.From;
205  string fromNamePrefix = "";
206  UUID fromID = UUID.Zero;
207  UUID ownerID = UUID.Zero;
208  string message = c.Message;
209  Scene scene = c.Scene as Scene;
210  UUID destination = c.Destination;
211  Vector3 fromPos = c.Position;
212  Vector3 regionPos = new Vector3(scene.RegionInfo.WorldLocX, scene.RegionInfo.WorldLocY, 0);
213 
214  bool checkParcelHide = false;
215  UUID sourceParcelID = UUID.Zero;
216  Vector3 hidePos = fromPos;
217 
218  if (c.Channel == DEBUG_CHANNEL) c.Type = ChatTypeEnum.DebugChannel;
219 
220  if(!m_scenes.Contains(scene))
221  {
222  m_log.WarnFormat("[CHAT]: message from unkown scene {0} ignored",
223  scene.RegionInfo.RegionName);
224  return;
225  }
226 
227  switch (sourceType)
228  {
229  case ChatSourceType.Agent:
230  ScenePresence avatar = (scene as Scene).GetScenePresence(c.Sender.AgentId);
231  fromPos = avatar.AbsolutePosition;
232  fromName = avatar.Name;
233  fromID = c.Sender.AgentId;
234  if (avatar.GodLevel >= 200)
235  { // let gods speak to outside or things may get confusing
236  fromNamePrefix = m_adminPrefix;
237  checkParcelHide = false;
238  }
239  else
240  {
241  checkParcelHide = true;
242  }
243  destination = UUID.Zero; // Avatars cant "SayTo"
244  ownerID = c.Sender.AgentId;
245 
246  hidePos = fromPos;
247  break;
248 
249  case ChatSourceType.Object:
250  fromID = c.SenderUUID;
251 
252  if (c.SenderObject != null && c.SenderObject is SceneObjectPart)
253  {
254  ownerID = ((SceneObjectPart)c.SenderObject).OwnerID;
255  if (((SceneObjectPart)c.SenderObject).ParentGroup.IsAttachment)
256  {
257  checkParcelHide = true;
258  hidePos = ((SceneObjectPart)c.SenderObject).ParentGroup.AbsolutePosition;
259  }
260  }
261  break;
262  }
263 
264  // TODO: iterate over message
265  if (message.Length >= 1000) // libomv limit
266  message = message.Substring(0, 1000);
267 
268 // m_log.DebugFormat(
269 // "[CHAT]: DCTA: fromID {0} fromName {1}, region{2}, cType {3}, sType {4}",
270 // fromID, fromName, scene.RegionInfo.RegionName, c.Type, sourceType);
271 
272  HashSet<UUID> receiverIDs = new HashSet<UUID>();
273 
274  if (checkParcelHide)
275  {
276  checkParcelHide = false;
277  if (c.Type < ChatTypeEnum.DebugChannel && destination == UUID.Zero)
278  {
279  ILandObject srcland = scene.LandChannel.GetLandObject(hidePos.X, hidePos.Y);
280  if (srcland != null && !srcland.LandData.SeeAVs)
281  {
282  sourceParcelID = srcland.LandData.GlobalID;
283  checkParcelHide = true;
284  }
285  }
286  }
287 
288  scene.ForEachScenePresence(
289  delegate(ScenePresence presence)
290  {
291  if (destination != UUID.Zero && presence.UUID != destination)
292  return;
293 
294  if(presence.IsChildAgent)
295  {
296  if(checkParcelHide)
297  return;
298  if (TrySendChatMessage(presence, fromPos, regionPos, fromID,
299  ownerID, fromNamePrefix + fromName, c.Type,
300  message, sourceType, (destination != UUID.Zero)))
301  receiverIDs.Add(presence.UUID);
302  return;
303  }
304 
305  ILandObject Presencecheck = scene.LandChannel.GetLandObject(presence.AbsolutePosition.X, presence.AbsolutePosition.Y);
306  if (Presencecheck != null)
307  {
308  if (checkParcelHide)
309  {
310  if (sourceParcelID != Presencecheck.LandData.GlobalID && presence.GodLevel < 200)
311  return;
312  }
313  if (c.Sender == null || Presencecheck.IsEitherBannedOrRestricted(c.Sender.AgentId) != true)
314  {
315  if (TrySendChatMessage(presence, fromPos, regionPos, fromID,
316  ownerID, fromNamePrefix + fromName, c.Type,
317  message, sourceType, (destination != UUID.Zero)))
318  receiverIDs.Add(presence.UUID);
319  }
320  }
321  });
322 
323  scene.EventManager.TriggerOnChatToClients(
324  fromID, receiverIDs, message, c.Type, fromPos, fromName, sourceType, ChatAudibleLevel.Fully);
325  }
326 
327  static private Vector3 CenterOfRegion = new Vector3(128, 128, 30);
328 
329  public virtual void OnChatBroadcast(Object sender, OSChatMessage c)
330  {
331  if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return;
332 
333  ChatTypeEnum cType = c.Type;
334  if (c.Channel == DEBUG_CHANNEL)
335  cType = ChatTypeEnum.DebugChannel;
336 
337  if (cType == ChatTypeEnum.Region)
338  cType = ChatTypeEnum.Say;
339 
340  if (c.Message.Length > 1100)
341  c.Message = c.Message.Substring(0, 1000);
342 
343  // broadcast chat works by redistributing every incoming chat
344  // message to each avatar in the scene.
345  string fromName = c.From;
346 
347  UUID fromID = UUID.Zero;
348  UUID ownerID = UUID.Zero;
349  ChatSourceType sourceType = ChatSourceType.Object;
350  if (null != c.Sender)
351  {
352  ScenePresence avatar = (c.Scene as Scene).GetScenePresence(c.Sender.AgentId);
353  fromID = c.Sender.AgentId;
354  fromName = avatar.Name;
355  ownerID = c.Sender.AgentId;
356  sourceType = ChatSourceType.Agent;
357  }
358  else if (c.SenderUUID != UUID.Zero)
359  {
360  fromID = c.SenderUUID;
361  ownerID = ((SceneObjectPart)c.SenderObject).OwnerID;
362  }
363 
364  // m_log.DebugFormat("[CHAT] Broadcast: fromID {0} fromName {1}, cType {2}, sType {3}", fromID, fromName, cType, sourceType);
365  HashSet<UUID> receiverIDs = new HashSet<UUID>();
366 
367  if (c.Scene != null)
368  {
369  ((Scene)c.Scene).ForEachRootClient
370  (
371  delegate(IClientAPI client)
372  {
373  // don't forward SayOwner chat from objects to
374  // non-owner agents
375  if ((c.Type == ChatTypeEnum.Owner) &&
376  (null != c.SenderObject) &&
377  (((SceneObjectPart)c.SenderObject).OwnerID != client.AgentId))
378  return;
379 
380  client.SendChatMessage(c.Message, (byte)cType, CenterOfRegion, fromName, fromID, fromID,
381  (byte)sourceType, (byte)ChatAudibleLevel.Fully);
382  receiverIDs.Add(client.AgentId);
383  }
384  );
386  fromID, receiverIDs, c.Message, cType, CenterOfRegion, fromName, sourceType, ChatAudibleLevel.Fully);
387  }
388  }
389 
407  protected virtual bool TrySendChatMessage(
408  ScenePresence presence, Vector3 fromPos, Vector3 regionPos,
409  UUID fromAgentID, UUID ownerID, string fromName, ChatTypeEnum type,
410  string message, ChatSourceType src, bool ignoreDistance)
411  {
412  if (presence.LifecycleState != ScenePresenceState.Running)
413  return false;
414 
415  if (!ignoreDistance)
416  {
417  Vector3 fromRegionPos = fromPos + regionPos;
418  Vector3 toRegionPos = presence.AbsolutePosition +
419  new Vector3(presence.Scene.RegionInfo.WorldLocX, presence.Scene.RegionInfo.WorldLocY, 0);
420 
421  int dis = (int)Util.GetDistanceTo(toRegionPos, fromRegionPos);
422 
423  if (type == ChatTypeEnum.Whisper && dis > m_whisperdistance ||
424  type == ChatTypeEnum.Say && dis > m_saydistance ||
425  type == ChatTypeEnum.Shout && dis > m_shoutdistance)
426  {
427  return false;
428  }
429  }
430 
431  // TODO: should change so the message is sent through the avatar rather than direct to the ClientView
432  presence.ControllingClient.SendChatMessage(
433  message, (byte) type, fromPos, fromName,
434  fromAgentID, ownerID, (byte)src, (byte)ChatAudibleLevel.Fully);
435 
436  return true;
437  }
438 
439  Dictionary<UUID, System.Threading.Timer> Timers = new Dictionary<UUID, System.Threading.Timer>();
440  public void ParcelFreezeUser(IClientAPI client, UUID parcelowner, uint flags, UUID target)
441  {
442  System.Threading.Timer Timer;
443  if (flags == 0)
444  {
445  FreezeCache.Add(target.ToString());
446  System.Threading.TimerCallback timeCB = new System.Threading.TimerCallback(OnEndParcelFrozen);
447  Timer = new System.Threading.Timer(timeCB, target, 30000, 0);
448  Timers.Add(target, Timer);
449  }
450  else
451  {
452  FreezeCache.Remove(target.ToString());
453  Timers.TryGetValue(target, out Timer);
454  Timers.Remove(target);
455  Timer.Dispose();
456  }
457  }
458 
459  private void OnEndParcelFrozen(object avatar)
460  {
461  UUID target = (UUID)avatar;
462  FreezeCache.Remove(target.ToString());
463  System.Threading.Timer Timer;
464  Timers.TryGetValue(target, out Timer);
465  Timers.Remove(target);
466  Timer.Dispose();
467  }
468  #region SimulatorFeaturesRequest
469 
470  static OSDInteger m_SayRange, m_WhisperRange, m_ShoutRange;
471 
472  private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features)
473  {
474  OSD extras = new OSDMap();
475  if (features.ContainsKey("OpenSimExtras"))
476  extras = features["OpenSimExtras"];
477  else
478  features["OpenSimExtras"] = extras;
479 
480  if (m_SayRange == null)
481  {
482  // Do this only once
483  m_SayRange = new OSDInteger(m_saydistance);
484  m_WhisperRange = new OSDInteger(m_whisperdistance);
485  m_ShoutRange = new OSDInteger(m_shoutdistance);
486  }
487 
488  ((OSDMap)extras)["say-range"] = m_SayRange;
489  ((OSDMap)extras)["whisper-range"] = m_WhisperRange;
490  ((OSDMap)extras)["shout-range"] = m_ShoutRange;
491 
492  }
493 
494  #endregion
495  }
496 }
void ParcelFreezeUser(IClientAPI client, UUID parcelowner, uint flags, UUID target)
Definition: ChatModule.cs:440
virtual void OnNewClient(IClientAPI client)
Definition: ChatModule.cs:150
Scene Scene
The scene to which this entity belongs
Definition: EntityBase.cs:46
virtual void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
Definition: ChatModule.cs:113
IClientAPI Sender
The client responsible for sending the message, or null.
OSChatMessage FixPositionOfChatMessage(OSChatMessage c)
Definition: ChatModule.cs:155
ScenePresenceState
The possible states that a scene presence can be in. This is currently orthagonal to whether a scene ...
OpenMetaverse.StructuredData.OSDMap OSDMap
virtual void OnChatBroadcast(Object sender, OSChatMessage c)
Definition: ChatModule.cs:329
Add remove or retrieve Simulator Features that will be given to a viewer via the SimulatorFeatures ca...
string Message
The message sent by the user
bool IsEitherBannedOrRestricted(UUID avatar)
System.Timers.Timer Timer
ScenePresence GetScenePresence(UUID agentID)
Request a scene presence by UUID. Fast, indexed lookup.
Definition: Scene.cs:5221
UUID GlobalID
Global ID for the parcel. (3rd Party Integration)
Definition: LandData.cs:327
virtual void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
Definition: ChatModule.cs:129
ChatFromViewer Arguments
object SenderObject
The object responsible for sending the message, or null.
virtual void OnChatFromWorld(Object sender, OSChatMessage c)
Definition: ChatModule.cs:194
ChatTypeEnum Type
The type of message, eg say, shout, broadcast.
OpenMetaverse.StructuredData.OSD OSD
virtual void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
Definition: ChatModule.cs:133
virtual void DeliverChatToAvatars(ChatSourceType sourceType, OSChatMessage c)
Definition: ChatModule.cs:202
virtual void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
Definition: ChatModule.cs:82
Interactive OpenSim region server
Definition: OpenSim.cs:55
A class for triggering remote scene events.
Definition: EventManager.cs:44
virtual void Initialise(IConfigSource config)
This is called to initialize the region module. For shared modules, this is called exactly once...
Definition: ChatModule.cs:62
int Channel
Which channel was this message sent on? Different channels may have different listeners. Public chat is on channel zero.
void TriggerOnChatFromClient(Object sender, OSChatMessage chat)
virtual bool TrySendChatMessage(ScenePresence presence, Vector3 fromPos, Vector3 regionPos, UUID fromAgentID, UUID ownerID, string fromName, ChatTypeEnum type, string message, ChatSourceType src, bool ignoreDistance)
Try to send a message to the given presence
Definition: ChatModule.cs:407
void TriggerOnChatToClients(UUID senderID, HashSet< UUID > receiverIDs, string message, ChatTypeEnum type, Vector3 fromPos, string fromName, ChatSourceType src, ChatAudibleLevel level)
ScenePresenceState LifecycleState
The current state of this presence. Governs only the existence lifecycle. See ScenePresenceStateMachi...
virtual void OnChatFromClient(Object sender, OSChatMessage c)
Definition: ChatModule.cs:165
virtual void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
Definition: ChatModule.cs:101