diff options
Diffstat (limited to 'OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs')
-rw-r--r-- | OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs | 293 |
1 files changed, 198 insertions, 95 deletions
diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs index f0b1e67..ea90185 100644 --- a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs | |||
@@ -45,17 +45,17 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
45 | private static readonly ILog m_log = | 45 | private static readonly ILog m_log = |
46 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 46 | LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
47 | 47 | ||
48 | private const int DEBUG_CHANNEL = 2147483647; | 48 | protected const int DEBUG_CHANNEL = 2147483647; |
49 | 49 | ||
50 | private bool m_enabled = true; | 50 | protected bool m_enabled = true; |
51 | private int m_saydistance = 20; | 51 | protected int m_saydistance = 20; |
52 | private int m_shoutdistance = 100; | 52 | protected int m_shoutdistance = 100; |
53 | private int m_whisperdistance = 10; | 53 | protected int m_whisperdistance = 10; |
54 | 54 | protected List<Scene> m_scenes = new List<Scene>(); | |
55 | internal object m_syncy = new object(); | 55 | protected List<string> FreezeCache = new List<string>(); |
56 | 56 | protected string m_adminPrefix = ""; | |
57 | internal IConfig m_config; | 57 | protected object m_syncy = new object(); |
58 | 58 | protected IConfig m_config; | |
59 | #region ISharedRegionModule Members | 59 | #region ISharedRegionModule Members |
60 | public virtual void Initialise(IConfigSource config) | 60 | public virtual void Initialise(IConfigSource config) |
61 | { | 61 | { |
@@ -69,21 +69,28 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
69 | m_enabled = false; | 69 | m_enabled = false; |
70 | return; | 70 | return; |
71 | } | 71 | } |
72 | } | ||
73 | 72 | ||
74 | m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); | 73 | m_whisperdistance = m_config.GetInt("whisper_distance", m_whisperdistance); |
75 | m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); | 74 | m_saydistance = m_config.GetInt("say_distance", m_saydistance); |
76 | m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); | 75 | m_shoutdistance = m_config.GetInt("shout_distance", m_shoutdistance); |
76 | m_adminPrefix = m_config.GetString("admin_prefix", ""); | ||
77 | } | ||
77 | } | 78 | } |
78 | 79 | ||
79 | public virtual void AddRegion(Scene scene) | 80 | public virtual void AddRegion(Scene scene) |
80 | { | 81 | { |
81 | if (!m_enabled) | 82 | if (!m_enabled) return; |
82 | return; | ||
83 | 83 | ||
84 | scene.EventManager.OnNewClient += OnNewClient; | 84 | lock (m_syncy) |
85 | scene.EventManager.OnChatFromWorld += OnChatFromWorld; | 85 | { |
86 | scene.EventManager.OnChatBroadcast += OnChatBroadcast; | 86 | if (!m_scenes.Contains(scene)) |
87 | { | ||
88 | m_scenes.Add(scene); | ||
89 | scene.EventManager.OnNewClient += OnNewClient; | ||
90 | scene.EventManager.OnChatFromWorld += OnChatFromWorld; | ||
91 | scene.EventManager.OnChatBroadcast += OnChatBroadcast; | ||
92 | } | ||
93 | } | ||
87 | 94 | ||
88 | m_log.InfoFormat("[CHAT]: Initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, | 95 | m_log.InfoFormat("[CHAT]: Initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, |
89 | m_whisperdistance, m_saydistance, m_shoutdistance); | 96 | m_whisperdistance, m_saydistance, m_shoutdistance); |
@@ -103,14 +110,20 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
103 | 110 | ||
104 | public virtual void RemoveRegion(Scene scene) | 111 | public virtual void RemoveRegion(Scene scene) |
105 | { | 112 | { |
106 | if (!m_enabled) | 113 | if (!m_enabled) return; |
107 | return; | ||
108 | 114 | ||
109 | scene.EventManager.OnNewClient -= OnNewClient; | 115 | lock (m_syncy) |
110 | scene.EventManager.OnChatFromWorld -= OnChatFromWorld; | 116 | { |
111 | scene.EventManager.OnChatBroadcast -= OnChatBroadcast; | 117 | if (m_scenes.Contains(scene)) |
118 | { | ||
119 | scene.EventManager.OnNewClient -= OnNewClient; | ||
120 | scene.EventManager.OnChatFromWorld -= OnChatFromWorld; | ||
121 | scene.EventManager.OnChatBroadcast -= OnChatBroadcast; | ||
122 | m_scenes.Remove(scene); | ||
123 | } | ||
124 | } | ||
112 | } | 125 | } |
113 | 126 | ||
114 | public virtual void Close() | 127 | public virtual void Close() |
115 | { | 128 | { |
116 | } | 129 | } |
@@ -119,7 +132,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
119 | { | 132 | { |
120 | } | 133 | } |
121 | 134 | ||
122 | public Type ReplaceableInterface | 135 | public virtual Type ReplaceableInterface |
123 | { | 136 | { |
124 | get { return null; } | 137 | get { return null; } |
125 | } | 138 | } |
@@ -137,7 +150,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
137 | client.OnChatFromClient += OnChatFromClient; | 150 | client.OnChatFromClient += OnChatFromClient; |
138 | } | 151 | } |
139 | 152 | ||
140 | protected OSChatMessage FixPositionOfChatMessage(OSChatMessage c) | 153 | protected virtual OSChatMessage FixPositionOfChatMessage(OSChatMessage c) |
141 | { | 154 | { |
142 | ScenePresence avatar; | 155 | ScenePresence avatar; |
143 | Scene scene = (Scene)c.Scene; | 156 | Scene scene = (Scene)c.Scene; |
@@ -165,7 +178,15 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
165 | return; | 178 | return; |
166 | } | 179 | } |
167 | 180 | ||
168 | DeliverChatToAvatars(ChatSourceType.Agent, c); | 181 | if (FreezeCache.Contains(c.Sender.AgentId.ToString())) |
182 | { | ||
183 | if (c.Type != ChatTypeEnum.StartTyping || c.Type != ChatTypeEnum.StopTyping) | ||
184 | c.Sender.SendAgentAlertMessage("You may not talk as you are frozen.", false); | ||
185 | } | ||
186 | else | ||
187 | { | ||
188 | DeliverChatToAvatars(ChatSourceType.Agent, c); | ||
189 | } | ||
169 | } | 190 | } |
170 | 191 | ||
171 | public virtual void OnChatFromWorld(Object sender, OSChatMessage c) | 192 | public virtual void OnChatFromWorld(Object sender, OSChatMessage c) |
@@ -179,34 +200,63 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
179 | protected virtual void DeliverChatToAvatars(ChatSourceType sourceType, OSChatMessage c) | 200 | protected virtual void DeliverChatToAvatars(ChatSourceType sourceType, OSChatMessage c) |
180 | { | 201 | { |
181 | string fromName = c.From; | 202 | string fromName = c.From; |
203 | string fromNamePrefix = ""; | ||
182 | UUID fromID = UUID.Zero; | 204 | UUID fromID = UUID.Zero; |
183 | UUID ownerID = UUID.Zero; | 205 | UUID ownerID = UUID.Zero; |
184 | UUID targetID = c.TargetUUID; | ||
185 | string message = c.Message; | 206 | string message = c.Message; |
186 | Scene scene = (Scene)c.Scene; | 207 | Scene scene = c.Scene as Scene; |
208 | UUID destination = c.Destination; | ||
187 | Vector3 fromPos = c.Position; | 209 | Vector3 fromPos = c.Position; |
188 | Vector3 regionPos = new Vector3(scene.RegionInfo.WorldLocX, scene.RegionInfo.WorldLocY, 0); | 210 | Vector3 regionPos = new Vector3(scene.RegionInfo.WorldLocX, scene.RegionInfo.WorldLocY, 0); |
189 | 211 | ||
212 | bool checkParcelHide = false; | ||
213 | UUID sourceParcelID = UUID.Zero; | ||
214 | Vector3 hidePos = fromPos; | ||
215 | |||
190 | if (c.Channel == DEBUG_CHANNEL) c.Type = ChatTypeEnum.DebugChannel; | 216 | if (c.Channel == DEBUG_CHANNEL) c.Type = ChatTypeEnum.DebugChannel; |
191 | 217 | ||
192 | switch (sourceType) | 218 | if(!m_scenes.Contains(scene)) |
193 | { | 219 | { |
194 | case ChatSourceType.Agent: | 220 | m_log.WarnFormat("[CHAT]: message from unkown scene {0} ignored", |
195 | ScenePresence avatar = scene.GetScenePresence(c.Sender.AgentId); | 221 | scene.RegionInfo.RegionName); |
196 | fromPos = avatar.AbsolutePosition; | 222 | return; |
197 | fromName = avatar.Name; | 223 | } |
198 | fromID = c.Sender.AgentId; | ||
199 | ownerID = c.Sender.AgentId; | ||
200 | 224 | ||
201 | break; | 225 | switch (sourceType) |
226 | { | ||
227 | case ChatSourceType.Agent: | ||
228 | ScenePresence avatar = (scene as Scene).GetScenePresence(c.Sender.AgentId); | ||
229 | fromPos = avatar.AbsolutePosition; | ||
230 | fromName = avatar.Name; | ||
231 | fromID = c.Sender.AgentId; | ||
232 | if (avatar.IsViewerUIGod) | ||
233 | { // let gods speak to outside or things may get confusing | ||
234 | fromNamePrefix = m_adminPrefix; | ||
235 | checkParcelHide = false; | ||
236 | } | ||
237 | else | ||
238 | { | ||
239 | checkParcelHide = true; | ||
240 | } | ||
241 | destination = UUID.Zero; // Avatars cant "SayTo" | ||
242 | ownerID = c.Sender.AgentId; | ||
202 | 243 | ||
203 | case ChatSourceType.Object: | 244 | hidePos = fromPos; |
204 | fromID = c.SenderUUID; | 245 | break; |
205 | 246 | ||
206 | if (c.SenderObject != null && c.SenderObject is SceneObjectPart) | 247 | case ChatSourceType.Object: |
207 | ownerID = ((SceneObjectPart)c.SenderObject).OwnerID; | 248 | fromID = c.SenderUUID; |
208 | 249 | ||
209 | break; | 250 | if (c.SenderObject != null && c.SenderObject is SceneObjectPart) |
251 | { | ||
252 | ownerID = ((SceneObjectPart)c.SenderObject).OwnerID; | ||
253 | if (((SceneObjectPart)c.SenderObject).ParentGroup.IsAttachment) | ||
254 | { | ||
255 | checkParcelHide = true; | ||
256 | hidePos = ((SceneObjectPart)c.SenderObject).ParentGroup.AbsolutePosition; | ||
257 | } | ||
258 | } | ||
259 | break; | ||
210 | } | 260 | } |
211 | 261 | ||
212 | // TODO: iterate over message | 262 | // TODO: iterate over message |
@@ -214,43 +264,66 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
214 | message = message.Substring(0, 1000); | 264 | message = message.Substring(0, 1000); |
215 | 265 | ||
216 | // m_log.DebugFormat( | 266 | // m_log.DebugFormat( |
217 | // "[CHAT]: DCTA: fromID {0} fromName {1}, region{2}, cType {3}, sType {4}, targetID {5}", | 267 | // "[CHAT]: DCTA: fromID {0} fromName {1}, region{2}, cType {3}, sType {4}", |
218 | // fromID, fromName, scene.RegionInfo.RegionName, c.Type, sourceType, targetID); | 268 | // fromID, fromName, scene.RegionInfo.RegionName, c.Type, sourceType); |
219 | 269 | ||
220 | HashSet<UUID> receiverIDs = new HashSet<UUID>(); | 270 | HashSet<UUID> receiverIDs = new HashSet<UUID>(); |
221 | 271 | ||
222 | if (targetID == UUID.Zero) | 272 | if (checkParcelHide) |
223 | { | 273 | { |
224 | // This should use ForEachClient, but clients don't have a position. | 274 | checkParcelHide = false; |
225 | // If camera is moved into client, then camera position can be used | 275 | if (c.Type < ChatTypeEnum.DebugChannel && destination == UUID.Zero) |
226 | scene.ForEachScenePresence( | 276 | { |
227 | delegate(ScenePresence presence) | 277 | ILandObject srcland = scene.LandChannel.GetLandObject(hidePos.X, hidePos.Y); |
278 | if (srcland != null && !srcland.LandData.SeeAVs) | ||
228 | { | 279 | { |
229 | if (TrySendChatMessage( | 280 | sourceParcelID = srcland.LandData.GlobalID; |
230 | presence, fromPos, regionPos, fromID, ownerID, fromName, c.Type, message, sourceType, false)) | 281 | checkParcelHide = true; |
231 | receiverIDs.Add(presence.UUID); | ||
232 | } | 282 | } |
233 | ); | ||
234 | } | ||
235 | else | ||
236 | { | ||
237 | // This is a send to a specific client eg from llRegionSayTo | ||
238 | // no need to check distance etc, jand send is as say | ||
239 | ScenePresence presence = scene.GetScenePresence(targetID); | ||
240 | if (presence != null && !presence.IsChildAgent) | ||
241 | { | ||
242 | if (TrySendChatMessage( | ||
243 | presence, fromPos, regionPos, fromID, ownerID, fromName, ChatTypeEnum.Say, message, sourceType, true)) | ||
244 | receiverIDs.Add(presence.UUID); | ||
245 | } | 283 | } |
246 | } | 284 | } |
247 | 285 | ||
286 | scene.ForEachScenePresence( | ||
287 | delegate(ScenePresence presence) | ||
288 | { | ||
289 | if (destination != UUID.Zero && presence.UUID != destination) | ||
290 | return; | ||
291 | |||
292 | if(presence.IsChildAgent) | ||
293 | { | ||
294 | if(checkParcelHide) | ||
295 | return; | ||
296 | if (TrySendChatMessage(presence, fromPos, regionPos, fromID, | ||
297 | ownerID, fromNamePrefix + fromName, c.Type, | ||
298 | message, sourceType, (destination != UUID.Zero))) | ||
299 | receiverIDs.Add(presence.UUID); | ||
300 | return; | ||
301 | } | ||
302 | |||
303 | ILandObject Presencecheck = scene.LandChannel.GetLandObject(presence.AbsolutePosition.X, presence.AbsolutePosition.Y); | ||
304 | if (Presencecheck != null) | ||
305 | { | ||
306 | if (checkParcelHide) | ||
307 | { | ||
308 | if (sourceParcelID != Presencecheck.LandData.GlobalID && !presence.IsViewerUIGod) | ||
309 | return; | ||
310 | } | ||
311 | if (c.Sender == null || Presencecheck.IsEitherBannedOrRestricted(c.Sender.AgentId) != true) | ||
312 | { | ||
313 | if (TrySendChatMessage(presence, fromPos, regionPos, fromID, | ||
314 | ownerID, fromNamePrefix + fromName, c.Type, | ||
315 | message, sourceType, (destination != UUID.Zero))) | ||
316 | receiverIDs.Add(presence.UUID); | ||
317 | } | ||
318 | } | ||
319 | }); | ||
320 | |||
248 | scene.EventManager.TriggerOnChatToClients( | 321 | scene.EventManager.TriggerOnChatToClients( |
249 | fromID, receiverIDs, message, c.Type, fromPos, fromName, sourceType, ChatAudibleLevel.Fully); | 322 | fromID, receiverIDs, message, c.Type, fromPos, fromName, sourceType, ChatAudibleLevel.Fully); |
250 | } | 323 | } |
251 | 324 | ||
252 | static private Vector3 CenterOfRegion = new Vector3(128, 128, 30); | 325 | static protected Vector3 CenterOfRegion = new Vector3(128, 128, 30); |
253 | 326 | ||
254 | public virtual void OnChatBroadcast(Object sender, OSChatMessage c) | 327 | public virtual void OnChatBroadcast(Object sender, OSChatMessage c) |
255 | { | 328 | { |
256 | if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return; | 329 | if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return; |
@@ -268,7 +341,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
268 | // broadcast chat works by redistributing every incoming chat | 341 | // broadcast chat works by redistributing every incoming chat |
269 | // message to each avatar in the scene. | 342 | // message to each avatar in the scene. |
270 | string fromName = c.From; | 343 | string fromName = c.From; |
271 | 344 | ||
272 | UUID fromID = UUID.Zero; | 345 | UUID fromID = UUID.Zero; |
273 | UUID ownerID = UUID.Zero; | 346 | UUID ownerID = UUID.Zero; |
274 | ChatSourceType sourceType = ChatSourceType.Object; | 347 | ChatSourceType sourceType = ChatSourceType.Object; |
@@ -280,35 +353,36 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
280 | ownerID = c.Sender.AgentId; | 353 | ownerID = c.Sender.AgentId; |
281 | sourceType = ChatSourceType.Agent; | 354 | sourceType = ChatSourceType.Agent; |
282 | } | 355 | } |
283 | else if (c.SenderUUID != UUID.Zero) | 356 | else if (c.SenderUUID != UUID.Zero) |
284 | { | 357 | { |
285 | fromID = c.SenderUUID; | 358 | fromID = c.SenderUUID; |
286 | ownerID = ((SceneObjectPart)c.SenderObject).OwnerID; | 359 | ownerID = ((SceneObjectPart)c.SenderObject).OwnerID; |
287 | } | 360 | } |
288 | |||
289 | // m_log.DebugFormat("[CHAT] Broadcast: fromID {0} fromName {1}, cType {2}, sType {3}", fromID, fromName, cType, sourceType); | ||
290 | 361 | ||
362 | // m_log.DebugFormat("[CHAT] Broadcast: fromID {0} fromName {1}, cType {2}, sType {3}", fromID, fromName, cType, sourceType); | ||
291 | HashSet<UUID> receiverIDs = new HashSet<UUID>(); | 363 | HashSet<UUID> receiverIDs = new HashSet<UUID>(); |
292 | |||
293 | ((Scene)c.Scene).ForEachRootClient( | ||
294 | delegate(IClientAPI client) | ||
295 | { | ||
296 | // don't forward SayOwner chat from objects to | ||
297 | // non-owner agents | ||
298 | if ((c.Type == ChatTypeEnum.Owner) && | ||
299 | (null != c.SenderObject) && | ||
300 | (((SceneObjectPart)c.SenderObject).OwnerID != client.AgentId)) | ||
301 | return; | ||
302 | 364 | ||
303 | client.SendChatMessage( | 365 | if (c.Scene != null) |
304 | c.Message, (byte)cType, CenterOfRegion, fromName, fromID, ownerID, | 366 | { |
305 | (byte)sourceType, (byte)ChatAudibleLevel.Fully); | 367 | ((Scene)c.Scene).ForEachRootClient |
306 | 368 | ( | |
307 | receiverIDs.Add(client.AgentId); | 369 | delegate(IClientAPI client) |
308 | }); | 370 | { |
309 | 371 | // don't forward SayOwner chat from objects to | |
310 | (c.Scene as Scene).EventManager.TriggerOnChatToClients( | 372 | // non-owner agents |
311 | fromID, receiverIDs, c.Message, cType, CenterOfRegion, fromName, sourceType, ChatAudibleLevel.Fully); | 373 | if ((c.Type == ChatTypeEnum.Owner) && |
374 | (null != c.SenderObject) && | ||
375 | (((SceneObjectPart)c.SenderObject).OwnerID != client.AgentId)) | ||
376 | return; | ||
377 | |||
378 | client.SendChatMessage(c.Message, (byte)cType, CenterOfRegion, fromName, fromID, fromID, | ||
379 | (byte)sourceType, (byte)ChatAudibleLevel.Fully); | ||
380 | receiverIDs.Add(client.AgentId); | ||
381 | } | ||
382 | ); | ||
383 | (c.Scene as Scene).EventManager.TriggerOnChatToClients( | ||
384 | fromID, receiverIDs, c.Message, cType, CenterOfRegion, fromName, sourceType, ChatAudibleLevel.Fully); | ||
385 | } | ||
312 | } | 386 | } |
313 | 387 | ||
314 | /// <summary> | 388 | /// <summary> |
@@ -326,7 +400,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
326 | /// <param name="type"></param> | 400 | /// <param name="type"></param> |
327 | /// <param name="message"></param> | 401 | /// <param name="message"></param> |
328 | /// <param name="src"></param> | 402 | /// <param name="src"></param> |
329 | /// <returns>true if the message was sent to the receiver, false if it was not sent due to failing a | 403 | /// <returns>true if the message was sent to the receiver, false if it was not sent due to failing a |
330 | /// precondition</returns> | 404 | /// precondition</returns> |
331 | protected virtual bool TrySendChatMessage( | 405 | protected virtual bool TrySendChatMessage( |
332 | ScenePresence presence, Vector3 fromPos, Vector3 regionPos, | 406 | ScenePresence presence, Vector3 fromPos, Vector3 regionPos, |
@@ -356,15 +430,44 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat | |||
356 | presence.ControllingClient.SendChatMessage( | 430 | presence.ControllingClient.SendChatMessage( |
357 | message, (byte) type, fromPos, fromName, | 431 | message, (byte) type, fromPos, fromName, |
358 | fromAgentID, ownerID, (byte)src, (byte)ChatAudibleLevel.Fully); | 432 | fromAgentID, ownerID, (byte)src, (byte)ChatAudibleLevel.Fully); |
359 | 433 | ||
360 | return true; | 434 | return true; |
361 | } | 435 | } |
362 | 436 | ||
437 | Dictionary<UUID, System.Threading.Timer> Timers = new Dictionary<UUID, System.Threading.Timer>(); | ||
438 | public virtual void ParcelFreezeUser(IClientAPI client, UUID parcelowner, uint flags, UUID target) | ||
439 | { | ||
440 | System.Threading.Timer Timer; | ||
441 | if (flags == 0) | ||
442 | { | ||
443 | FreezeCache.Add(target.ToString()); | ||
444 | System.Threading.TimerCallback timeCB = new System.Threading.TimerCallback(OnEndParcelFrozen); | ||
445 | Timer = new System.Threading.Timer(timeCB, target, 30000, 0); | ||
446 | Timers.Add(target, Timer); | ||
447 | } | ||
448 | else | ||
449 | { | ||
450 | FreezeCache.Remove(target.ToString()); | ||
451 | Timers.TryGetValue(target, out Timer); | ||
452 | Timers.Remove(target); | ||
453 | Timer.Dispose(); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | protected virtual void OnEndParcelFrozen(object avatar) | ||
458 | { | ||
459 | UUID target = (UUID)avatar; | ||
460 | FreezeCache.Remove(target.ToString()); | ||
461 | System.Threading.Timer Timer; | ||
462 | Timers.TryGetValue(target, out Timer); | ||
463 | Timers.Remove(target); | ||
464 | Timer.Dispose(); | ||
465 | } | ||
363 | #region SimulatorFeaturesRequest | 466 | #region SimulatorFeaturesRequest |
364 | 467 | ||
365 | static OSDInteger m_SayRange, m_WhisperRange, m_ShoutRange; | 468 | protected static OSDInteger m_SayRange, m_WhisperRange, m_ShoutRange; |
366 | 469 | ||
367 | private void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) | 470 | protected virtual void OnSimulatorFeaturesRequest(UUID agentID, ref OSDMap features) |
368 | { | 471 | { |
369 | OSD extras = new OSDMap(); | 472 | OSD extras = new OSDMap(); |
370 | if (features.ContainsKey("OpenSimExtras")) | 473 | if (features.ContainsKey("OpenSimExtras")) |