clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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.Linq;
31 using System.Reflection;
32 using System.Threading;
33 using OpenMetaverse;
34 using log4net;
35 using log4net.Appender;
36 using log4net.Core;
37 using log4net.Repository;
38 using Nini.Config;
39 using OpenSim.Framework;
40 using OpenSim.Framework.Console;
41 using OpenSim.Framework.Monitoring;
42 using pCampBot.Interfaces;
43  
44 namespace pCampBot
45 {
46 public enum BotManagerBotConnectingState
47 {
48 Initializing,
49 Ready,
50 Connecting,
51 Disconnecting
52 }
53  
54 /// <summary>
55 /// Thread/Bot manager for the application
56 /// </summary>
57 public class BotManager
58 {
59 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
60  
61 public const int DefaultLoginDelay = 5000;
62  
63 /// <summary>
64 /// Is pCampbot ready to connect or currently in the process of connecting or disconnecting bots?
65 /// </summary>
66 public BotManagerBotConnectingState BotConnectingState { get; private set; }
67  
68 /// <summary>
69 /// Used to control locking as we can't lock an enum.
70 /// </summary>
71 private object BotConnectingStateChangeObject = new object();
72  
73 /// <summary>
74 /// Delay between logins of multiple bots.
75 /// </summary>
76 /// <remarks>TODO: This value needs to be configurable by a command line argument.</remarks>
77 public int LoginDelay { get; set; }
78  
79 /// <summary>
80 /// Command console
81 /// </summary>
82 protected CommandConsole m_console;
83  
84 /// <summary>
85 /// Controls whether bots start out sending agent updates on connection.
86 /// </summary>
87 public bool InitBotSendAgentUpdates { get; set; }
88  
89 /// <summary>
90 /// Controls whether bots request textures for the object information they receive
91 /// </summary>
92 public bool InitBotRequestObjectTextures { get; set; }
93  
94 /// <summary>
95 /// Created bots, whether active or inactive.
96 /// </summary>
97 protected List<Bot> m_bots;
98  
99 /// <summary>
100 /// Random number generator.
101 /// </summary>
102 public Random Rng { get; private set; }
103  
104 /// <summary>
105 /// Track the assets we have and have not received so we don't endlessly repeat requests.
106 /// </summary>
107 public Dictionary<UUID, bool> AssetsReceived { get; private set; }
108  
109 /// <summary>
110 /// The regions that we know about.
111 /// </summary>
112 public Dictionary<ulong, GridRegion> RegionsKnown { get; private set; }
113  
114 /// <summary>
115 /// First name for bots
116 /// </summary>
117 private string m_firstName;
118  
119 /// <summary>
120 /// Last name stem for bots
121 /// </summary>
122 private string m_lastNameStem;
123  
124 /// <summary>
125 /// Password for bots
126 /// </summary>
127 private string m_password;
128  
129 /// <summary>
130 /// Login URI for bots.
131 /// </summary>
132 private string m_loginUri;
133  
134 /// <summary>
135 /// Start location for bots.
136 /// </summary>
137 private string m_startUri;
138  
139 /// <summary>
140 /// Postfix bot number at which bot sequence starts.
141 /// </summary>
142 private int m_fromBotNumber;
143  
144 /// <summary>
145 /// Wear setting for bots.
146 /// </summary>
147 private string m_wearSetting;
148  
149 /// <summary>
150 /// Behaviour switches for bots.
151 /// </summary>
152 private HashSet<string> m_defaultBehaviourSwitches = new HashSet<string>();
153  
154 /// <summary>
155 /// Collects general information on this server (which reveals this to be a misnamed class).
156 /// </summary>
157 private ServerStatsCollector m_serverStatsCollector;
158  
159 /// <summary>
160 /// Constructor Creates MainConsole.Instance to take commands and provide the place to write data
161 /// </summary>
162 public BotManager()
163 {
164 // We set this to avoid issues with bots running out of HTTP connections if many are run from a single machine
165 // to multiple regions.
166 Settings.MAX_HTTP_CONNECTIONS = int.MaxValue;
167  
168 // System.Threading.ThreadPool.SetMaxThreads(600, 240);
169 //
170 // int workerThreads, iocpThreads;
171 // System.Threading.ThreadPool.GetMaxThreads(out workerThreads, out iocpThreads);
172 // Console.WriteLine("ThreadPool.GetMaxThreads {0} {1}", workerThreads, iocpThreads);
173  
174 InitBotSendAgentUpdates = true;
175 InitBotRequestObjectTextures = true;
176  
177 LoginDelay = DefaultLoginDelay;
178  
179 Rng = new Random(Environment.TickCount);
180 AssetsReceived = new Dictionary<UUID, bool>();
181 RegionsKnown = new Dictionary<ulong, GridRegion>();
182  
183 m_console = CreateConsole();
184 MainConsole.Instance = m_console;
185  
186 // Make log4net see the console
187 //
188 ILoggerRepository repository = LogManager.GetRepository();
189 IAppender[] appenders = repository.GetAppenders();
190 OpenSimAppender consoleAppender = null;
191  
192 foreach (IAppender appender in appenders)
193 {
194 if (appender.Name == "Console")
195 {
196 consoleAppender = (OpenSimAppender)appender;
197 consoleAppender.Console = m_console;
198 break;
199 }
200 }
201  
202 m_console.Commands.AddCommand(
203 "Bots", false, "shutdown", "shutdown", "Shutdown bots and exit", HandleShutdown);
204  
205 m_console.Commands.AddCommand(
206 "Bots", false, "quit", "quit", "Shutdown bots and exit", HandleShutdown);
207  
208 m_console.Commands.AddCommand(
209 "Bots", false, "connect", "connect [<n>]", "Connect bots",
210 "If an <n> is given, then the first <n> disconnected bots by postfix number are connected.\n"
211 + "If no <n> is given, then all currently disconnected bots are connected.",
212 HandleConnect);
213  
214 m_console.Commands.AddCommand(
215 "Bots", false, "disconnect", "disconnect [<n>]", "Disconnect bots",
216 "Disconnecting bots will interupt any bot connection process, including connection on startup.\n"
217 + "If an <n> is given, then the last <n> connected bots by postfix number are disconnected.\n"
218 + "If no <n> is given, then all currently connected bots are disconnected.",
219 HandleDisconnect);
220  
221 m_console.Commands.AddCommand(
222 "Bots", false, "add behaviour", "add behaviour <abbreviated-name> [<bot-number>]",
223 "Add a behaviour to a bot",
224 "If no bot number is specified then behaviour is added to all bots.\n"
225 + "Can be performed on connected or disconnected bots.",
226 HandleAddBehaviour);
227  
228 m_console.Commands.AddCommand(
229 "Bots", false, "remove behaviour", "remove behaviour <abbreviated-name> [<bot-number>]",
230 "Remove a behaviour from a bot",
231 "If no bot number is specified then behaviour is added to all bots.\n"
232 + "Can be performed on connected or disconnected bots.",
233 HandleRemoveBehaviour);
234  
235 m_console.Commands.AddCommand(
236 "Bots", false, "sit", "sit", "Sit all bots on the ground.",
237 HandleSit);
238  
239 m_console.Commands.AddCommand(
240 "Bots", false, "stand", "stand", "Stand all bots.",
241 HandleStand);
242  
243 m_console.Commands.AddCommand(
244 "Bots", false, "set bots", "set bots <key> <value>", "Set a setting for all bots.", HandleSetBots);
245  
246 m_console.Commands.AddCommand(
247 "Bots", false, "show regions", "show regions", "Show regions known to bots", HandleShowRegions);
248  
249 m_console.Commands.AddCommand(
250 "Bots", false, "show bots", "show bots", "Shows the status of all bots.", HandleShowBotsStatus);
251  
252 m_console.Commands.AddCommand(
253 "Bots", false, "show bot", "show bot <bot-number>",
254 "Shows the detailed status and settings of a particular bot.", HandleShowBotStatus);
255  
256 m_console.Commands.AddCommand(
257 "Bots", false, "show status", "show status", "Shows pCampbot status.", HandleShowStatus);
258  
259 m_bots = new List<Bot>();
260  
261 Watchdog.Enabled = true;
262 StatsManager.RegisterConsoleCommands(m_console);
263  
264 m_serverStatsCollector = new ServerStatsCollector();
265 m_serverStatsCollector.Initialise(null);
266 m_serverStatsCollector.Enabled = true;
267 m_serverStatsCollector.Start();
268  
269 BotConnectingState = BotManagerBotConnectingState.Ready;
270 }
271  
272 /// <summary>
273 /// Startup number of bots specified in the starting arguments
274 /// </summary>
275 /// <param name="botcount">How many bots to start up</param>
276 /// <param name="cs">The configuration for the bots to use</param>
277 public void CreateBots(int botcount, IConfig startupConfig)
278 {
279 m_firstName = startupConfig.GetString("firstname");
280 m_lastNameStem = startupConfig.GetString("lastname");
281 m_password = startupConfig.GetString("password");
282 m_loginUri = startupConfig.GetString("loginuri");
283 m_fromBotNumber = startupConfig.GetInt("from", 0);
284 m_wearSetting = startupConfig.GetString("wear", "no");
285  
286 m_startUri = ParseInputStartLocationToUri(startupConfig.GetString("start", "last"));
287  
288 Array.ForEach<string>(
289 startupConfig.GetString("behaviours", "p").Split(new char[] { ',' }), b => m_defaultBehaviourSwitches.Add(b));
290  
291 for (int i = 0; i < botcount; i++)
292 {
293 lock (m_bots)
294 {
295 string lastName = string.Format("{0}_{1}", m_lastNameStem, i + m_fromBotNumber);
296  
297 CreateBot(
298 this,
299 CreateBehavioursFromAbbreviatedNames(m_defaultBehaviourSwitches),
300 m_firstName, lastName, m_password, m_loginUri, m_startUri, m_wearSetting);
301 }
302 }
303 }
304  
305 private List<IBehaviour> CreateBehavioursFromAbbreviatedNames(HashSet<string> abbreviatedNames)
306 {
307 // We must give each bot its own list of instantiated behaviours since they store state.
308 List<IBehaviour> behaviours = new List<IBehaviour>();
309  
310 // Hard-coded for now
311 foreach (string abName in abbreviatedNames)
312 {
313 IBehaviour newBehaviour = null;
314  
315 if (abName == "c")
316 newBehaviour = new CrossBehaviour();
317  
318 if (abName == "g")
319 newBehaviour = new GrabbingBehaviour();
320  
321 if (abName == "n")
322 newBehaviour = new NoneBehaviour();
323  
324 if (abName == "p")
325 newBehaviour = new PhysicsBehaviour();
326  
327 if (abName == "t")
328 newBehaviour = new TeleportBehaviour();
329  
330 if (abName == "tw")
331 newBehaviour = new TwitchyBehaviour();
332  
333 if (abName == "ph2")
334 newBehaviour = new PhysicsBehaviour2();
335  
336 if (newBehaviour != null)
337 {
338 behaviours.Add(newBehaviour);
339 }
340 else
341 {
342 MainConsole.Instance.OutputFormat("No behaviour with abbreviated name {0} found", abName);
343 }
344 }
345  
346 return behaviours;
347 }
348  
349 public void ConnectBots(int botcount)
350 {
351 lock (BotConnectingStateChangeObject)
352 {
353 if (BotConnectingState != BotManagerBotConnectingState.Ready)
354 {
355 MainConsole.Instance.OutputFormat(
356 "Bot connecting status is {0}. Please wait for previous process to complete.", BotConnectingState);
357 return;
358 }
359  
360 BotConnectingState = BotManagerBotConnectingState.Connecting;
361 }
362  
363 Thread connectBotThread = new Thread(o => ConnectBotsInternal(botcount));
364  
365 connectBotThread.Name = "Bots connection thread";
366 connectBotThread.Start();
367 }
368  
369 private void ConnectBotsInternal(int botCount)
370 {
371 m_log.InfoFormat(
372 "[BOT MANAGER]: Starting {0} bots connecting to {1}, location {2}, named {3} {4}_<n>",
373 botCount,
374 m_loginUri,
375 m_startUri,
376 m_firstName,
377 m_lastNameStem);
378  
379 m_log.DebugFormat("[BOT MANAGER]: Delay between logins is {0}ms", LoginDelay);
380 m_log.DebugFormat("[BOT MANAGER]: BotsSendAgentUpdates is {0}", InitBotSendAgentUpdates);
381 m_log.DebugFormat("[BOT MANAGER]: InitBotRequestObjectTextures is {0}", InitBotRequestObjectTextures);
382  
383 List<Bot> botsToConnect = new List<Bot>();
384  
385 lock (m_bots)
386 {
387 foreach (Bot bot in m_bots)
388 {
389 if (bot.ConnectionState == ConnectionState.Disconnected)
390 botsToConnect.Add(bot);
391  
392 if (botsToConnect.Count >= botCount)
393 break;
394 }
395 }
396  
397 foreach (Bot bot in botsToConnect)
398 {
399 lock (BotConnectingStateChangeObject)
400 {
401 if (BotConnectingState != BotManagerBotConnectingState.Connecting)
402 {
403 MainConsole.Instance.Output(
404 "[BOT MANAGER]: Aborting bot connection due to user-initiated disconnection");
405 return;
406 }
407 }
408  
409 bot.Connect();
410  
411 // Stagger logins
412 Thread.Sleep(LoginDelay);
413 }
414  
415 lock (BotConnectingStateChangeObject)
416 {
417 if (BotConnectingState == BotManagerBotConnectingState.Connecting)
418 BotConnectingState = BotManagerBotConnectingState.Ready;
419 }
420 }
421  
422 /// <summary>
423 /// Parses the command line start location to a start string/uri that the login mechanism will recognize.
424 /// </summary>
425 /// <returns>
426 /// The input start location to URI.
427 /// </returns>
428 /// <param name='startLocation'>
429 /// Start location.
430 /// </param>
431 private string ParseInputStartLocationToUri(string startLocation)
432 {
433 if (startLocation == "home" || startLocation == "last")
434 return startLocation;
435  
436 string regionName;
437  
438 // Just a region name or only one (!) extra component. Like a viewer, we will stick 128/128/0 on the end
439 Vector3 startPos = new Vector3(128, 128, 0);
440  
441 string[] startLocationComponents = startLocation.Split('/');
442  
443 regionName = startLocationComponents[0];
444  
445 if (startLocationComponents.Length >= 2)
446 {
447 float.TryParse(startLocationComponents[1], out startPos.X);
448  
449 if (startLocationComponents.Length >= 3)
450 {
451 float.TryParse(startLocationComponents[2], out startPos.Y);
452  
453 if (startLocationComponents.Length >= 4)
454 float.TryParse(startLocationComponents[3], out startPos.Z);
455 }
456 }
457  
458 return string.Format("uri:{0}&{1}&{2}&{3}", regionName, startPos.X, startPos.Y, startPos.Z);
459 }
460  
461 /// <summary>
462 /// This creates a bot but does not start it.
463 /// </summary>
464 /// <param name="bm"></param>
465 /// <param name="behaviours">Behaviours for this bot to perform.</param>
466 /// <param name="firstName">First name</param>
467 /// <param name="lastName">Last name</param>
468 /// <param name="password">Password</param>
469 /// <param name="loginUri">Login URI</param>
470 /// <param name="startLocation">Location to start the bot. Can be "last", "home" or a specific sim name.</param>
471 /// <param name="wearSetting"></param>
472 public void CreateBot(
473 BotManager bm, List<IBehaviour> behaviours,
474 string firstName, string lastName, string password, string loginUri, string startLocation, string wearSetting)
475 {
476 MainConsole.Instance.OutputFormat(
477 "[BOT MANAGER]: Creating bot {0} {1}, behaviours are {2}",
478 firstName, lastName, string.Join(",", behaviours.ConvertAll<string>(b => b.Name).ToArray()));
479  
480 Bot pb = new Bot(bm, behaviours, firstName, lastName, password, startLocation, loginUri);
481 pb.wear = wearSetting;
482 pb.Client.Settings.SEND_AGENT_UPDATES = InitBotSendAgentUpdates;
483 pb.RequestObjectTextures = InitBotRequestObjectTextures;
484  
485 pb.OnConnected += handlebotEvent;
486 pb.OnDisconnected += handlebotEvent;
487  
488 m_bots.Add(pb);
489 }
490  
491 /// <summary>
492 /// High level connnected/disconnected events so we can keep track of our threads by proxy
493 /// </summary>
494 /// <param name="callbot"></param>
495 /// <param name="eventt"></param>
496 private void handlebotEvent(Bot callbot, EventType eventt)
497 {
498 switch (eventt)
499 {
500 case EventType.CONNECTED:
501 {
502 m_log.Info("[" + callbot.FirstName + " " + callbot.LastName + "]: Connected");
503 break;
504 }
505  
506 case EventType.DISCONNECTED:
507 {
508 m_log.Info("[" + callbot.FirstName + " " + callbot.LastName + "]: Disconnected");
509 break;
510 }
511 }
512 }
513  
514 /// <summary>
515 /// Standard CreateConsole routine
516 /// </summary>
517 /// <returns></returns>
518 protected CommandConsole CreateConsole()
519 {
520 return new LocalConsole("pCampbot");
521 }
522  
523 private void HandleConnect(string module, string[] cmd)
524 {
525 lock (m_bots)
526 {
527 int botsToConnect;
528 int disconnectedBots = m_bots.Count(b => b.ConnectionState == ConnectionState.Disconnected);
529  
530 if (cmd.Length == 1)
531 {
532 botsToConnect = disconnectedBots;
533 }
534 else
535 {
536 if (!ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, cmd[1], out botsToConnect))
537 return;
538  
539 botsToConnect = Math.Min(botsToConnect, disconnectedBots);
540 }
541  
542 MainConsole.Instance.OutputFormat("Connecting {0} bots", botsToConnect);
543  
544 ConnectBots(botsToConnect);
545 }
546 }
547  
548 private void HandleAddBehaviour(string module, string[] cmd)
549 {
550 if (cmd.Length < 3 || cmd.Length > 4)
551 {
552 MainConsole.Instance.OutputFormat("Usage: add behaviour <abbreviated-behaviour> [<bot-number>]");
553 return;
554 }
555  
556 string rawBehaviours = cmd[2];
557  
558 List<Bot> botsToEffect = new List<Bot>();
559  
560 if (cmd.Length == 3)
561 {
562 lock (m_bots)
563 botsToEffect.AddRange(m_bots);
564 }
565 else
566 {
567 int botNumber;
568 if (!ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, cmd[3], out botNumber))
569 return;
570  
571 Bot bot = GetBotFromNumber(botNumber);
572  
573 if (bot == null)
574 {
575 MainConsole.Instance.OutputFormat("Error: No bot found with number {0}", botNumber);
576 return;
577 }
578  
579 botsToEffect.Add(bot);
580 }
581  
582  
583 HashSet<string> rawAbbreviatedSwitchesToAdd = new HashSet<string>();
584 Array.ForEach<string>(rawBehaviours.Split(new char[] { ',' }), b => rawAbbreviatedSwitchesToAdd.Add(b));
585  
586 foreach (Bot bot in botsToEffect)
587 {
588 List<IBehaviour> behavioursAdded = new List<IBehaviour>();
589  
590 foreach (IBehaviour behaviour in CreateBehavioursFromAbbreviatedNames(rawAbbreviatedSwitchesToAdd))
591 {
592 if (bot.AddBehaviour(behaviour))
593 behavioursAdded.Add(behaviour);
594 }
595  
596 MainConsole.Instance.OutputFormat(
597 "Added behaviours {0} to bot {1}",
598 string.Join(", ", behavioursAdded.ConvertAll<string>(b => b.Name).ToArray()), bot.Name);
599 }
600 }
601  
602 private void HandleRemoveBehaviour(string module, string[] cmd)
603 {
604 if (cmd.Length < 3 || cmd.Length > 4)
605 {
606 MainConsole.Instance.OutputFormat("Usage: remove behaviour <abbreviated-behaviour> [<bot-number>]");
607 return;
608 }
609  
610 string rawBehaviours = cmd[2];
611  
612 List<Bot> botsToEffect = new List<Bot>();
613  
614 if (cmd.Length == 3)
615 {
616 lock (m_bots)
617 botsToEffect.AddRange(m_bots);
618 }
619 else
620 {
621 int botNumber;
622 if (!ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, cmd[3], out botNumber))
623 return;
624  
625 Bot bot = GetBotFromNumber(botNumber);
626  
627 if (bot == null)
628 {
629 MainConsole.Instance.OutputFormat("Error: No bot found with number {0}", botNumber);
630 return;
631 }
632  
633 botsToEffect.Add(bot);
634 }
635  
636 HashSet<string> abbreviatedBehavioursToRemove = new HashSet<string>();
637 Array.ForEach<string>(rawBehaviours.Split(new char[] { ',' }), b => abbreviatedBehavioursToRemove.Add(b));
638  
639 foreach (Bot bot in botsToEffect)
640 {
641 List<IBehaviour> behavioursRemoved = new List<IBehaviour>();
642  
643 foreach (string b in abbreviatedBehavioursToRemove)
644 {
645 IBehaviour behaviour;
646  
647 if (bot.TryGetBehaviour(b, out behaviour))
648 {
649 bot.RemoveBehaviour(b);
650 behavioursRemoved.Add(behaviour);
651 }
652 }
653  
654 MainConsole.Instance.OutputFormat(
655 "Removed behaviours {0} from bot {1}",
656 string.Join(", ", behavioursRemoved.ConvertAll<string>(b => b.Name).ToArray()), bot.Name);
657 }
658 }
659  
660 private void HandleDisconnect(string module, string[] cmd)
661 {
662 List<Bot> connectedBots;
663 int botsToDisconnectCount;
664  
665 lock (m_bots)
666 connectedBots = m_bots.FindAll(b => b.ConnectionState == ConnectionState.Connected);
667  
668 if (cmd.Length == 1)
669 {
670 botsToDisconnectCount = connectedBots.Count;
671 }
672 else
673 {
674 if (!ConsoleUtil.TryParseConsoleNaturalInt(MainConsole.Instance, cmd[1], out botsToDisconnectCount))
675 return;
676  
677 botsToDisconnectCount = Math.Min(botsToDisconnectCount, connectedBots.Count);
678 }
679  
680 lock (BotConnectingStateChangeObject)
681 BotConnectingState = BotManagerBotConnectingState.Disconnecting;
682  
683 Thread disconnectBotThread = new Thread(o => DisconnectBotsInternal(connectedBots, botsToDisconnectCount));
684  
685 disconnectBotThread.Name = "Bots disconnection thread";
686 disconnectBotThread.Start();
687 }
688  
689 private void DisconnectBotsInternal(List<Bot> connectedBots, int disconnectCount)
690 {
691 MainConsole.Instance.OutputFormat("Disconnecting {0} bots", disconnectCount);
692  
693 int disconnectedBots = 0;
694  
695 for (int i = connectedBots.Count - 1; i >= 0; i--)
696 {
697 if (disconnectedBots >= disconnectCount)
698 break;
699  
700 Bot thisBot = connectedBots[i];
701  
702 if (thisBot.ConnectionState == ConnectionState.Connected)
703 {
704 ThreadPool.QueueUserWorkItem(o => thisBot.Disconnect());
705 disconnectedBots++;
706 }
707 }
708  
709 lock (BotConnectingStateChangeObject)
710 BotConnectingState = BotManagerBotConnectingState.Ready;
711 }
712  
713 private void HandleSit(string module, string[] cmd)
714 {
715 lock (m_bots)
716 {
717 foreach (Bot bot in m_bots)
718 {
719 if (bot.ConnectionState == ConnectionState.Connected)
720 {
721 MainConsole.Instance.OutputFormat("Sitting bot {0} on ground.", bot.Name);
722 bot.SitOnGround();
723 }
724 }
725 }
726 }
727  
728 private void HandleStand(string module, string[] cmd)
729 {
730 lock (m_bots)
731 {
732 foreach (Bot bot in m_bots)
733 {
734 if (bot.ConnectionState == ConnectionState.Connected)
735 {
736 MainConsole.Instance.OutputFormat("Standing bot {0} from ground.", bot.Name);
737 bot.Stand();
738 }
739 }
740 }
741 }
742  
743 private void HandleShutdown(string module, string[] cmd)
744 {
745 lock (m_bots)
746 {
747 int connectedBots = m_bots.Count(b => b.ConnectionState == ConnectionState.Connected);
748  
749 if (connectedBots > 0)
750 {
751 MainConsole.Instance.OutputFormat("Please disconnect {0} connected bots first", connectedBots);
752 return;
753 }
754 }
755  
756 MainConsole.Instance.Output("Shutting down");
757  
758 m_serverStatsCollector.Close();
759  
760 Environment.Exit(0);
761 }
762  
763 private void HandleSetBots(string module, string[] cmd)
764 {
765 string key = cmd[2];
766 string rawValue = cmd[3];
767  
768 if (key == "SEND_AGENT_UPDATES")
769 {
770 bool newSendAgentUpdatesSetting;
771  
772 if (!ConsoleUtil.TryParseConsoleBool(MainConsole.Instance, rawValue, out newSendAgentUpdatesSetting))
773 return;
774  
775 MainConsole.Instance.OutputFormat(
776 "Setting SEND_AGENT_UPDATES to {0} for all bots", newSendAgentUpdatesSetting);
777  
778 lock (m_bots)
779 m_bots.ForEach(b => b.Client.Settings.SEND_AGENT_UPDATES = newSendAgentUpdatesSetting);
780 }
781 else
782 {
783 MainConsole.Instance.Output("Error: Only setting currently available is SEND_AGENT_UPDATES");
784 }
785 }
786  
787 private void HandleShowRegions(string module, string[] cmd)
788 {
789 string outputFormat = "{0,-30} {1, -20} {2, -5} {3, -5}";
790 MainConsole.Instance.OutputFormat(outputFormat, "Name", "Handle", "X", "Y");
791  
792 lock (RegionsKnown)
793 {
794 foreach (GridRegion region in RegionsKnown.Values)
795 {
796 MainConsole.Instance.OutputFormat(
797 outputFormat, region.Name, region.RegionHandle, region.X, region.Y);
798 }
799 }
800 }
801  
802 private void HandleShowStatus(string module, string[] cmd)
803 {
804 ConsoleDisplayList cdl = new ConsoleDisplayList();
805 cdl.AddRow("Bot connecting state", BotConnectingState);
806  
807 MainConsole.Instance.Output(cdl.ToString());
808 }
809  
810 private void HandleShowBotsStatus(string module, string[] cmd)
811 {
812 ConsoleDisplayTable cdt = new ConsoleDisplayTable();
813 cdt.AddColumn("Name", 24);
814 cdt.AddColumn("Region", 24);
815 cdt.AddColumn("Status", 13);
816 cdt.AddColumn("Conns", 5);
817 cdt.AddColumn("Behaviours", 20);
818  
819 Dictionary<ConnectionState, int> totals = new Dictionary<ConnectionState, int>();
820 foreach (object o in Enum.GetValues(typeof(ConnectionState)))
821 totals[(ConnectionState)o] = 0;
822  
823 lock (m_bots)
824 {
825 foreach (Bot bot in m_bots)
826 {
827 Simulator currentSim = bot.Client.Network.CurrentSim;
828 totals[bot.ConnectionState]++;
829  
830 cdt.AddRow(
831 bot.Name,
832 currentSim != null ? currentSim.Name : "(none)",
833 bot.ConnectionState,
834 bot.SimulatorsCount,
835 string.Join(",", bot.Behaviours.Keys.ToArray()));
836 }
837 }
838  
839 MainConsole.Instance.Output(cdt.ToString());
840  
841 ConsoleDisplayList cdl = new ConsoleDisplayList();
842  
843 foreach (KeyValuePair<ConnectionState, int> kvp in totals)
844 cdl.AddRow(kvp.Key, kvp.Value);
845  
846 MainConsole.Instance.Output(cdl.ToString());
847 }
848  
849 private void HandleShowBotStatus(string module, string[] cmd)
850 {
851 if (cmd.Length != 3)
852 {
853 MainConsole.Instance.Output("Usage: show bot <n>");
854 return;
855 }
856  
857 int botNumber;
858  
859 if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, cmd[2], out botNumber))
860 return;
861  
862 Bot bot = GetBotFromNumber(botNumber);
863  
864 if (bot == null)
865 {
866 MainConsole.Instance.OutputFormat("Error: No bot found with number {0}", botNumber);
867 return;
868 }
869  
870 ConsoleDisplayList cdl = new ConsoleDisplayList();
871 cdl.AddRow("Name", bot.Name);
872 cdl.AddRow("Status", bot.ConnectionState);
873  
874 Simulator currentSim = bot.Client.Network.CurrentSim;
875 cdl.AddRow("Region", currentSim != null ? currentSim.Name : "(none)");
876  
877 List<Simulator> connectedSimulators = bot.Simulators;
878 List<string> simulatorNames = connectedSimulators.ConvertAll<string>(cs => cs.Name);
879 cdl.AddRow("Connections", string.Join(", ", simulatorNames.ToArray()));
880  
881 MainConsole.Instance.Output(cdl.ToString());
882  
883 MainConsole.Instance.Output("Settings");
884  
885 ConsoleDisplayList statusCdl = new ConsoleDisplayList();
886  
887 statusCdl.AddRow(
888 "Behaviours",
889 string.Join(", ", bot.Behaviours.Values.ToList().ConvertAll<string>(b => b.Name).ToArray()));
890  
891 GridClient botClient = bot.Client;
892 statusCdl.AddRow("SEND_AGENT_UPDATES", botClient.Settings.SEND_AGENT_UPDATES);
893  
894 MainConsole.Instance.Output(statusCdl.ToString());
895 }
896  
897 /// <summary>
898 /// Get a specific bot from its number.
899 /// </summary>
900 /// <returns>null if no bot was found</returns>
901 /// <param name='botNumber'></param>
902 private Bot GetBotFromNumber(int botNumber)
903 {
904 string name = GenerateBotNameFromNumber(botNumber);
905  
906 Bot bot;
907  
908 lock (m_bots)
909 bot = m_bots.Find(b => b.Name == name);
910  
911 return bot;
912 }
913  
914 private string GenerateBotNameFromNumber(int botNumber)
915 {
916 return string.Format("{0} {1}_{2}", m_firstName, m_lastNameStem, botNumber);
917 }
918  
919 internal void Grid_GridRegion(object o, GridRegionEventArgs args)
920 {
921 lock (RegionsKnown)
922 {
923 GridRegion newRegion = args.Region;
924  
925 if (RegionsKnown.ContainsKey(newRegion.RegionHandle))
926 {
927 return;
928 }
929 else
930 {
931 m_log.DebugFormat(
932 "[BOT MANAGER]: Adding {0} {1} to known regions", newRegion.Name, newRegion.RegionHandle);
933 RegionsKnown[newRegion.RegionHandle] = newRegion;
934 }
935 }
936 }
937 }
938 }