WorldCommModule.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  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 OpenSim 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. using System;
  28. using System.Collections;
  29. using libsecondlife;
  30. using Nini.Config;
  31. using OpenSim.Framework;
  32. using OpenSim.Region.Environment.Interfaces;
  33. using OpenSim.Region.Environment.Scenes;
  34. /*****************************************************
  35. *
  36. * WorldCommModule
  37. *
  38. *
  39. * Holding place for world comms - basically llListen
  40. * function implementation.
  41. *
  42. * lLListen(integer channel, string name, key id, string msg)
  43. * The name, id, and msg arguments specify the filtering
  44. * criteria. You can pass the empty string
  45. * (or NULL_KEY for id) for these to set a completely
  46. * open filter; this causes the listen() event handler to be
  47. * invoked for all chat on the channel. To listen only
  48. * for chat spoken by a specific object or avatar,
  49. * specify the name and/or id arguments. To listen
  50. * only for a specific command, specify the
  51. * (case-sensitive) msg argument. If msg is not empty,
  52. * listener will only hear strings which are exactly equal
  53. * to msg. You can also use all the arguments to establish
  54. * the most restrictive filtering criteria.
  55. *
  56. * It might be useful for each listener to maintain a message
  57. * digest, with a list of recent messages by UUID. This can
  58. * be used to prevent in-world repeater loops. However, the
  59. * linden functions do not have this capability, so for now
  60. * thats the way it works.
  61. *
  62. * **************************************************/
  63. namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm
  64. {
  65. public class WorldCommModule : IRegionModule, IWorldComm
  66. {
  67. private object CommListLock = new object();
  68. private object ListLock = new object();
  69. private ListenerManager m_listenerManager;
  70. private string m_name = "WorldCommModule";
  71. private Queue m_pending;
  72. private Queue m_pendingQ;
  73. private Scene m_scene;
  74. public WorldCommModule()
  75. {
  76. }
  77. #region IRegionModule Members
  78. public void Initialise(Scene scene, IConfigSource config)
  79. {
  80. m_scene = scene;
  81. m_scene.RegisterModuleInterface<IWorldComm>(this);
  82. m_listenerManager = new ListenerManager();
  83. m_scene.EventManager.OnNewClient += NewClient;
  84. m_pendingQ = new Queue();
  85. m_pending = Queue.Synchronized(m_pendingQ);
  86. }
  87. public void PostInitialise()
  88. {
  89. }
  90. public void Close()
  91. {
  92. }
  93. public string Name
  94. {
  95. get { return m_name; }
  96. }
  97. public bool IsSharedModule
  98. {
  99. get { return false; }
  100. }
  101. #endregion
  102. #region IWorldComm Members
  103. public int Listen(uint localID, LLUUID itemID, LLUUID hostID, int channel, string name, string id, string msg)
  104. {
  105. return m_listenerManager.AddListener(localID, itemID, hostID, channel, name, id, msg);
  106. }
  107. public void ListenControl(int handle, int active)
  108. {
  109. if (m_listenerManager != null)
  110. {
  111. if (active == 1)
  112. m_listenerManager.Activate(handle);
  113. else if (active == 0)
  114. m_listenerManager.Dectivate(handle);
  115. }
  116. }
  117. public void ListenRemove(int handle)
  118. {
  119. if (m_listenerManager != null)
  120. {
  121. m_listenerManager.Remove(handle);
  122. }
  123. }
  124. public void DeleteListener(LLUUID itemID)
  125. {
  126. if (m_listenerManager != null)
  127. {
  128. m_listenerManager.DeleteListener(itemID);
  129. }
  130. }
  131. // This method scans nearby objects and determines if they are listeners,
  132. // and if so if this message fits the filter. If it does, then
  133. // enqueue the message for delivery to the objects listen event handler.
  134. // Objects that do an llSay have their messages delivered here, and for
  135. // nearby avatars, the SimChat function is used.
  136. public void DeliverMessage(string sourceItemID, ChatTypeEnum type, int channel, string name, string msg)
  137. {
  138. SceneObjectPart source = null;
  139. ScenePresence avatar = null;
  140. source = m_scene.GetSceneObjectPart(new LLUUID(sourceItemID));
  141. if (source == null)
  142. {
  143. avatar = m_scene.GetScenePresence(new LLUUID(sourceItemID));
  144. }
  145. if ((avatar != null) || (source != null))
  146. {
  147. // Loop through the objects in the scene
  148. // If they are in proximity, then if they are
  149. // listeners, if so add them to the pending queue
  150. foreach (ListenerInfo li in m_listenerManager.GetListeners())
  151. {
  152. EntityBase sPart;
  153. m_scene.Entities.TryGetValue(li.GetHostID(), out sPart);
  154. if (sPart != null)
  155. {
  156. double dis = 0;
  157. if (source != null)
  158. dis = Util.GetDistanceTo(sPart.AbsolutePosition, source.AbsolutePosition);
  159. else
  160. dis = Util.GetDistanceTo(sPart.AbsolutePosition, avatar.AbsolutePosition);
  161. switch (type)
  162. {
  163. case ChatTypeEnum.Whisper:
  164. if ((dis < 10) && (dis > -10))
  165. {
  166. if (li.GetChannel() == channel)
  167. {
  168. ListenerInfo isListener = m_listenerManager.IsListenerMatch(
  169. sourceItemID, sPart.UUID, channel, name, msg
  170. );
  171. if (isListener != null)
  172. {
  173. lock (m_pending.SyncRoot)
  174. {
  175. m_pending.Enqueue(isListener);
  176. }
  177. }
  178. }
  179. }
  180. break;
  181. case ChatTypeEnum.Say:
  182. if ((dis < 30) && (dis > -30))
  183. {
  184. if (li.GetChannel() == channel)
  185. {
  186. ListenerInfo isListener = m_listenerManager.IsListenerMatch(
  187. sourceItemID, sPart.UUID, channel, name, msg
  188. );
  189. if (isListener != null)
  190. {
  191. lock (m_pending.SyncRoot)
  192. {
  193. m_pending.Enqueue(isListener);
  194. }
  195. }
  196. }
  197. }
  198. break;
  199. case ChatTypeEnum.Shout:
  200. if ((dis < 100) && (dis > -100))
  201. {
  202. if (li.GetChannel() == channel)
  203. {
  204. ListenerInfo isListener = m_listenerManager.IsListenerMatch(
  205. sourceItemID, sPart.UUID, channel, name, msg
  206. );
  207. if (isListener != null)
  208. {
  209. lock (m_pending.SyncRoot)
  210. {
  211. m_pending.Enqueue(isListener);
  212. }
  213. }
  214. }
  215. }
  216. break;
  217. case ChatTypeEnum.Broadcast:
  218. // Dont process if this message is from itself!
  219. if (li.GetHostID().ToString().Equals(sourceItemID) ||
  220. sPart.UUID.ToString().Equals(sourceItemID))
  221. continue;
  222. if (li.GetChannel() == channel)
  223. {
  224. ListenerInfo isListener = m_listenerManager.IsListenerMatch(
  225. sourceItemID, sPart.UUID, channel, name, msg
  226. );
  227. if (isListener != null)
  228. {
  229. lock (m_pending.SyncRoot)
  230. {
  231. m_pending.Enqueue(isListener);
  232. }
  233. }
  234. }
  235. break;
  236. }
  237. }
  238. }
  239. }
  240. }
  241. public bool HasMessages()
  242. {
  243. if (m_pending != null)
  244. return (m_pending.Count > 0);
  245. else
  246. return false;
  247. }
  248. public ListenerInfo GetNextMessage()
  249. {
  250. ListenerInfo li = null;
  251. lock (m_pending.SyncRoot)
  252. {
  253. li = (ListenerInfo) m_pending.Dequeue();
  254. }
  255. return li;
  256. }
  257. public uint PeekNextMessageLocalID()
  258. {
  259. return ((ListenerInfo) m_pending.Peek()).GetLocalID();
  260. }
  261. public LLUUID PeekNextMessageItemID()
  262. {
  263. return ((ListenerInfo) m_pending.Peek()).GetItemID();
  264. }
  265. #endregion
  266. public void NewClient(IClientAPI client)
  267. {
  268. client.OnChatFromViewer += DeliverClientMessage;
  269. }
  270. /********************************************************************
  271. *
  272. * Listener Stuff
  273. *
  274. * *****************************************************************/
  275. private void DeliverClientMessage(Object sender, ChatFromViewerArgs e)
  276. {
  277. DeliverMessage(e.Sender.AgentId.ToString(),
  278. e.Type, e.Channel,
  279. e.Sender.FirstName + " " + e.Sender.LastName,
  280. e.Message);
  281. }
  282. }
  283. public class ListenerManager
  284. {
  285. //private Dictionary<int, ListenerInfo> m_listeners;
  286. private object ListenersLock = new object();
  287. private Hashtable m_listeners = Hashtable.Synchronized(new Hashtable());
  288. private int m_MaxListeners = 100;
  289. public int AddListener(uint localID, LLUUID itemID, LLUUID hostID, int channel, string name, string id, string msg)
  290. {
  291. if (m_listeners.Count < m_MaxListeners)
  292. {
  293. ListenerInfo isListener = IsListenerMatch(LLUUID.Zero.ToString(), itemID, channel, name, msg);
  294. if (isListener == null)
  295. {
  296. int newHandle = GetNewHandle();
  297. if (newHandle > -1)
  298. {
  299. ListenerInfo li = new ListenerInfo(localID, newHandle, itemID, hostID, channel, name, id, msg);
  300. lock (m_listeners.SyncRoot)
  301. {
  302. m_listeners.Add(newHandle, li);
  303. }
  304. return newHandle;
  305. }
  306. }
  307. }
  308. return -1;
  309. }
  310. public void Remove(int handle)
  311. {
  312. lock (m_listeners.SyncRoot)
  313. {
  314. m_listeners.Remove(handle);
  315. }
  316. }
  317. public void DeleteListener(LLUUID itemID)
  318. {
  319. ArrayList removedListeners = new ArrayList();
  320. lock (m_listeners.SyncRoot)
  321. {
  322. IDictionaryEnumerator en = m_listeners.GetEnumerator();
  323. while (en.MoveNext())
  324. {
  325. ListenerInfo li = (ListenerInfo) en.Value;
  326. if (li.GetItemID().Equals(itemID))
  327. {
  328. removedListeners.Add(li.GetHandle());
  329. }
  330. }
  331. foreach (int handle in removedListeners)
  332. {
  333. m_listeners.Remove(handle);
  334. }
  335. }
  336. }
  337. private int GetNewHandle()
  338. {
  339. for (int i = 0; i < int.MaxValue - 1; i++)
  340. {
  341. if (!m_listeners.ContainsKey(i))
  342. return i;
  343. }
  344. return -1;
  345. }
  346. public bool IsListener(LLUUID hostID)
  347. {
  348. foreach (ListenerInfo li in m_listeners.Values)
  349. {
  350. if (li.GetHostID().Equals(hostID))
  351. return true;
  352. }
  353. return false;
  354. }
  355. public void Activate(int handle)
  356. {
  357. if (m_listeners.ContainsKey(handle))
  358. {
  359. lock (m_listeners.SyncRoot)
  360. {
  361. ListenerInfo li = (ListenerInfo) m_listeners[handle];
  362. li.Activate();
  363. }
  364. }
  365. }
  366. public void Dectivate(int handle)
  367. {
  368. if (m_listeners.ContainsKey(handle))
  369. {
  370. ListenerInfo li = (ListenerInfo) m_listeners[handle];
  371. li.Deactivate();
  372. }
  373. }
  374. // Theres probably a more clever and efficient way to
  375. // do this, maybe with regex.
  376. public ListenerInfo IsListenerMatch(string sourceItemID, LLUUID listenerKey, int channel, string name,
  377. string msg)
  378. {
  379. bool isMatch = true;
  380. lock (m_listeners.SyncRoot)
  381. {
  382. IDictionaryEnumerator en = m_listeners.GetEnumerator();
  383. while (en.MoveNext())
  384. {
  385. ListenerInfo li = (ListenerInfo) en.Value;
  386. if (li.IsActive())
  387. {
  388. if (li.GetHostID().Equals(listenerKey))
  389. {
  390. if (channel == li.GetChannel())
  391. {
  392. if ((li.GetID().ToString().Length > 0) &&
  393. (!li.GetID().Equals(LLUUID.Zero)))
  394. {
  395. if (!li.GetID().ToString().Equals(sourceItemID))
  396. {
  397. isMatch = false;
  398. }
  399. }
  400. if (isMatch && (li.GetName().Length > 0))
  401. {
  402. if (li.GetName().Equals(name))
  403. {
  404. isMatch = false;
  405. }
  406. }
  407. if (isMatch)
  408. {
  409. return new ListenerInfo(
  410. li.GetLocalID(), li.GetHandle(), li.GetItemID(), li.GetHostID(),
  411. li.GetChannel(), name, li.GetID(), msg, new LLUUID(sourceItemID)
  412. );
  413. }
  414. }
  415. }
  416. }
  417. }
  418. }
  419. return null;
  420. }
  421. public ICollection GetListeners()
  422. {
  423. return m_listeners.Values;
  424. }
  425. }
  426. public class ListenerInfo
  427. {
  428. private bool m_active; // Listener is active or not
  429. private int m_channel; // Channel
  430. private int m_handle; // Assigned handle of this listener
  431. private LLUUID m_hostID; // ID of the host/scene part
  432. private LLUUID m_id; // ID to filter messages from
  433. private LLUUID m_itemID; // ID of the host script engine
  434. private uint m_localID; // Local ID from script engine
  435. private string m_message; // The message
  436. private string m_name; // Object name to filter messages from
  437. private LLUUID m_sourceItemID; // ID of the scenePart or avatar source of the message
  438. public ListenerInfo(uint localID, int handle, LLUUID ItemID, LLUUID hostID, int channel, string name, LLUUID id, string message)
  439. {
  440. Initialise(localID, handle, ItemID, hostID, channel, name, id, message);
  441. }
  442. public ListenerInfo(uint localID, int handle, LLUUID ItemID, LLUUID hostID, int channel, string name, LLUUID id,
  443. string message, LLUUID sourceItemID)
  444. {
  445. Initialise(localID, handle, ItemID, hostID, channel, name, id, message);
  446. m_sourceItemID = sourceItemID;
  447. }
  448. private void Initialise(uint localID, int handle, LLUUID ItemID, LLUUID hostID, int channel, string name,
  449. LLUUID id, string message)
  450. {
  451. m_handle = handle;
  452. m_channel = channel;
  453. m_itemID = ItemID;
  454. m_hostID = hostID;
  455. m_name = name;
  456. m_id = id;
  457. m_message = message;
  458. m_active = true;
  459. m_localID = localID;
  460. }
  461. public LLUUID GetItemID()
  462. {
  463. return m_itemID;
  464. }
  465. public LLUUID GetHostID()
  466. {
  467. return m_hostID;
  468. }
  469. public LLUUID GetSourceItemID()
  470. {
  471. return m_sourceItemID;
  472. }
  473. public int GetChannel()
  474. {
  475. return m_channel;
  476. }
  477. public uint GetLocalID()
  478. {
  479. return m_localID;
  480. }
  481. public int GetHandle()
  482. {
  483. return m_handle;
  484. }
  485. public string GetMessage()
  486. {
  487. return m_message;
  488. }
  489. public string GetName()
  490. {
  491. return m_name;
  492. }
  493. public bool IsActive()
  494. {
  495. return m_active;
  496. }
  497. public void Deactivate()
  498. {
  499. m_active = false;
  500. }
  501. public void Activate()
  502. {
  503. m_active = true;
  504. }
  505. public LLUUID GetID()
  506. {
  507. return m_id;
  508. }
  509. }
  510. }