public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject moduleOutput;

         switch(command.Command)
         {
            case "me":
               moduleOutput = new ModuleJSONObject();
               moduleOutput.broadcast = true;
               moduleOutput.message = user.Username + " " + command.Arguments[0]; //System.Security.SecurityElement.Escape(command.Arguments[0]);
               moduleOutput.tag = command.tag;
               outputs.Add(moduleOutput);
               break;

            case "emotes":
               moduleOutput = new ModuleJSONObject();
               moduleOutput.message = "Emote list:\n";
             
               try
               {
                  string htmlCode;

                  using (WebClient client = new WebClient())
                  {
                     htmlCode = client.DownloadString(GetOption<string>("emoteLink"));
                  }

                  EmoteJSONObject emotes = JsonConvert.DeserializeObject<EmoteJSONObject>(htmlCode);

                  foreach(string emote in emotes.mapping.Keys)
                     moduleOutput.message += "\n" + emotes.format.Replace("emote", emote);
               }
               catch (Exception e)
               {
                  moduleOutput.message = "Sorry, could not retrieve emote list!\nException: " + e.ToString();
               }

               outputs.Add(moduleOutput);
               break;
         }

         return outputs;
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject output = new ModuleJSONObject();
         string error = "";

         switch (command.Command)
         {
            case "pm":
               //First, make sure this even works
               UserInfo recipient;
               output = new ModuleJSONObject();

               if (!GetUserFromArgument(command.Arguments[0], 
                      users.Where(x => x.Value.LoggedIn).ToDictionary(x => x.Key, y => y.Value), out recipient))
               {
                  AddError(outputs);
                  break;
               }
               
               output.tag = "any";
               output.message = user.Username + " -> " + command.Arguments[0] + ":\n" + command.Arguments[1];
                  //System.Security.SecurityElement.Escape(command.Arguments[1]);
               output.recipients.Add(recipient.UID);
               output.recipients.Add(command.uid);
               outputs.Add(output);
               break;
            case "pmcreateroom":
               HashSet<int> roomUsers = new HashSet<int>();

               foreach (string roomUser in command.ArgumentParts[0])
               {
                  UserInfo tempUser;
                  if (!GetUserFromArgument(roomUser, users, out tempUser))
                  {
                     error = "User not found!";
                     break;
                  }
                  else if (!roomUsers.Add(tempUser.UID))
                  {
                     error = "Duplicate user in list: " + tempUser.Username;
                     break;
                  }
               }

               roomUsers.Add(user.UID);

               if (!string.IsNullOrWhiteSpace(error) || !ChatRunner.Server.CreatePMRoom(roomUsers, user.UID, out error))
               {
                  WarningJSONObject warning = new WarningJSONObject(error);
                  outputs.Add(warning);
               }
               else
               {
                  output.message = "You created a chatroom for " + string.Join(", ", roomUsers.Select(x => users[x].Username));
                  outputs.Add(output);
               }

               break;

            case "pmleaveroom":
               if (!ChatRunner.Server.LeavePMRoom(user.UID, command.tag, out error))
               {
                  WarningJSONObject warning = new WarningJSONObject(error);
                  outputs.Add(warning);
               }
               else
               {
                  output.message = "You left this PM room";
                  outputs.Add(output);
               }
               break;
         }

         return outputs;
      }
      //THIS is the function you're looking for. When the chat server detects a command for your module, it passes it along
      //to this function. You can process the command here, then send the output as the return object. Notice that the return
      //is a list; you can return multiple messages per command. This function is called ONCE for each command detected. 
      //"user" is the user who performed the command; the class UserInfo contains a lot of information about a user, so
      //use it as needed. The provided dictionary is a list of ALL users ever seen by the chat, so it's like the chat server's
      //database. Each UserInfo object has a "LoggedIn" field, so you can tell if they're logged in or not. "command" has
      //two important fields: "Command" and "Arguments". Command is simply the string containing just the command performed,
      //and Arguments is a list (array) of the arguments, each as a string. The returned messages are in a JSON format, which
      //is masked by the JSONObject class. You will almost ALWAYS be returning one or more ModuleJSONObjects, which is a 
      //chat server message specific to modules. You can also return system messages, etc. All the X_JSONObjects classes like
      //ModuleJSONObject derive from the JSONObject class, so you can return any of them. The "message" field of the 
      //ModuleJSONObject holds the output, and you can change the recipient by adding users (as integers) to the "recipients"
      //field. If you do not specify any recipients, it defaults to the sender (this is usually what you want). If you
      //want to broadcast a message to everyone (please don't do this for every command), you can set the "broadcast" field
      //to true.
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         //This is what we're returning from our function
         List<JSONObject> outputs = new List<JSONObject>();

         //Our commands (and probably yours too) usually returns just one message, so that's what this is.
         //It gets added to the above "outputs" list.
         ModuleJSONObject moduleOutput = new ModuleJSONObject();

         List<UserStatistics> allStats = userStatistics.Select(x => x.Value).ToList();

         //Run different code depending on the command (duh)
         switch(command.Command)
         {
            case "mystatistics":
               //Always use the UID, NOT the username to identifiy users. Usernames can change.
               if (!userStatistics.ContainsKey(user.UID))
               {
                  //Here, we're setting the message that will be displayed for this chat message object.
                  moduleOutput.message = "You have no statistics yet. You will after this message!";
               }
               else
               {
                  moduleOutput.message = GetUserStats(user);
               }

               //We add just the one output to our list of outputs (we only want to output one thing)
               outputs.Add(moduleOutput);
               break;

            case "statistics":
               if (command.Arguments.Count == 0)
               {
                  moduleOutput.message = "---Global Chat Statistics---\n";
                  moduleOutput.message += "Total messages: " + allStats.Sum(x => x.TotalMessages) + "\n";
                  moduleOutput.message += "Average message size: " + (allStats.Count == 0 ? 0 : (int)(allStats.Sum(x => x.AverageMessageLength) / allStats.Count)) + " characters\n";
                  moduleOutput.message += "Total users seen: " + allStats.Count + "\n";
                  moduleOutput.message += "Total user chat time: " + StringExtensions.LargestTime(new TimeSpan(users.Sum(x => x.Value.TotalChatTime.Ticks))) + "\n";
                  moduleOutput.message += "Average session time: " + (users.Count == 0 ? "error" : StringExtensions.LargestTime(new TimeSpan(users.Sum(x => x.Value.AverageSessionTime.Ticks) / users.Count))) + "\n";
                  outputs.Add(moduleOutput);
               }
               else
               {
                  //THIS IS IMPORTANT! Arguments containing a user will be given to you as a USERNAME! However, you want
                  //to store their information based on their UID. This function "GetUserFromArgument()" will search through
                  //the given dictionary to find you the user with the given username. It should always succeed, and puts the
                  //result in the "out" parameter. You probably don't need to check the return of GetUserFromArgument like I
                  //do, but just to be save, if it fails, you can just fail with a generic error. AddError adds a generic
                  //module error to the given list of outputs, then you can return outputs and it'll contain that error.
                  UserInfo findUser;
                  if (!GetUserFromArgument(command.Arguments[0], users, out findUser))
                  {
                     AddError(outputs);
                     break;
                  }

                  moduleOutput.message = GetUserStats(findUser);
                  outputs.Add(moduleOutput);
               }
               break;
         }

         return outputs;
      }
      public override List<ChatEssentials.JSONObject> ProcessCommand(UserCommand command, ChatEssentials.UserInfo user, Dictionary<int, ChatEssentials.UserInfo> users)
      {
         #region oneTimeProcess
         if(!oneTimeRun)
         {
            oneTimeRun = true;
            //Cheating = 
            ExplorerConstants.Simulation.FruitGrowthHours = GetOption<double>("fruitHours");
            ExplorerConstants.Simulation.TreeGrowthHours = GetOption<double>("treeHours");
            ExplorerConstants.Simulation.StoneGrowthHours = GetOption<double>("stoneHours");
            ExplorerConstants.Simulation.SuperFruitGrowthHours = GetOption<double>("superFruitHours");
            ExplorerConstants.Items.TorchSteps = GetOption<int>("torchSteps");
            ExplorerConstants.Probability.CaveChance = GetOption<double>("caveChance");
            ExplorerConstants.Player.HourlyStaminaRegain = GetOption<int>("hourlyStamina");
            //ExplorerConstants.Player.FruitFullnessIncrease = GetOption<double>("fullnessIncrease");
            ExplorerConstants.Player.FruitPerFullness = GetOption<double>("fruitPerFullness");
            ExplorerConstants.Player.FullnessDecayMinutes = GetOption<double>("fullnessDecayMinutes");
            ExplorerConstants.Items.ExtraFruitPerMagicStone = GetOption<double>("extraFruitPerMagicStone");

            string temp = GetOption<string>("player");
            if (temp.Length > 0)
               ExplorerConstants.Player.CurrentPlayerToken = temp[0];

            if (GetOption<bool>("cheating"))
               Commands.Add(new ModuleCommand("excheat", new List<CommandArgument>(), "Get a crapload of resources"));
         }
         #endregion

         #region fromPostProcess
         if (worlds.Count == 0)
         {
            GenerateWorld();
         }
            
         foreach (WorldInstance world in worlds)
            world.RefreshFullness((DateTime.Now - lastCommand).TotalMinutes / ExplorerConstants.Player.FullnessDecayMinutes);

         lastCommand = DateTime.Now;

         if ((DateTime.Now - lastRefresh).TotalHours >= 1.0 / ExplorerConstants.Player.HourlyStaminaRegain)
         {
            foreach (WorldInstance world in worlds)
               world.RefreshStamina((int)Math.Ceiling((DateTime.Now - lastRefresh).TotalHours * ExplorerConstants.Player.HourlyStaminaRegain));

            lastRefresh = DateTime.Now;
         }

         foreach (WorldInstance world in worlds)
            world.SimulateTime();
         #endregion

			Match match;
			Tuple<WorldInstance, string> results;

         TryRegister(user.UID);

			try
			{
            if(command.Command == "exmastertest")
            {
               if(!user.ChatControlExtended)
                  return FastMessage("You don't have access to this command", true);

               int worldType;
               if (!int.TryParse(command.Arguments[0], out worldType))
                  worldType = 0;

               World world = new World();
               world.Generate(worldType % ExplorerConstants.Generation.PresetBases.Count);
               world.GetFullMapImage(false).Save(StringExtensions.PathFixer(GetOption<string>("mapFolder")) + "test" + worldType + ".png");

               return FastMessage("Test " + worldType + " map: " + GetOption<string>("mapLink") + "/test" + worldType + ".png");
            }

            if(command.Command == "exmasterflagscatter")
            {
               if(!user.ChatControlExtended)
                  return FastMessage("You don't have access to this command", true);

               int amount, world;
               if (!int.TryParse(command.Arguments[0], out amount) || amount > 10000)
                  return FastMessage("You can't spawn that many flags!");
               if (!int.TryParse(command.Arguments[1], out world) || !worlds.Any(x => x.Operating && x.WorldID == world))
                  return FastMessage("The world you gave was invalid!");

               int actualCount = worlds.First(x => x.Operating && x.WorldID == world).WorldData.ScatterFlags(amount);

               ModuleJSONObject broadcast = new ModuleJSONObject("Hey, " + user.Username + " has just spawned " + actualCount +
                  " flags in exgame world " + world + "!");
               broadcast.broadcast = true;
               
               return new List<JSONObject>{ broadcast };
            }

				#region mastergenerate
				//match = Regex.Match(chunk.Message, @"^\s*/exmastergenerate\s*([0-9]+)?\s*$");
            if (command.Command == "exmastergenerate")
				{
               if(!user.ChatControlExtended)
                  return FastMessage("You don't have access to this command", true);

					int givenCode;
               if (!int.TryParse(command.Arguments[0], out givenCode))
						givenCode = 0;

					if (generateCode == -1)
					{
						generateCode = (int)(DateTime.Now.Ticks % 10000);
                  return FastMessage("If you wish to generate a new world, type in the same command with this code: " + generateCode);
					}
					else
					{
						if (generateCode != givenCode)
						{
							generateCode = -1;
                     return FastMessage("That was the wrong code. Code has been reset", true);
						}
						GenerateWorld();
						generateCode = -1;
                  return FastMessage("You've generated a new world!");
					}
				}
				#endregion

				#region masterclose
				//match = Regex.Match(chunk.Message, @"^\s*/exmasterclose\s+([0-9]+)\s*([0-9]+)?\s*$");
            if (command.Command == "exmasterclose")//match.Success)
				{
//					if (chunk.Username != Module.AllControlUsername)
//						return chunk.Username + ", you don't have access to this command";
               if(!user.ChatControlExtended)
                  return FastMessage("You don't have access to this command", true);

					int givenCode, givenWorld;

					if (!int.TryParse(command.Arguments[0], out givenWorld) || !worlds.Any(x => x.Operating && x.WorldID == givenWorld))
                  return FastMessage("The world you gave was invalid!", true);

               if (!int.TryParse(command.Arguments[1], out givenCode))
						givenCode = 0;

					if (closeCode == -1)
					{
						closeCode = (int)(DateTime.Now.Ticks % 10000);
						closeWorld = givenWorld;
                  return FastMessage("If you wish to close world " + closeWorld + " , type in the same command with this code: " + closeCode);
					}
					else
					{
						if (closeCode != givenCode || closeWorld != givenWorld)
						{
							closeCode = -1;
							closeWorld = -1;
                     return FastMessage("That was the wrong code or world. Code has been reset", true);
						}

						string output = "You've closed world " + closeWorld;
                  bool warning = false;
						WorldInstance world = worlds.FirstOrDefault(x => x.WorldID == closeWorld);
						if (world == null)
                  {
							output = "Something went wrong. No worlds have been closed";
                     warning = true;
                  }
						else
                  {
							world.CloseWorld();
                  }

						closeWorld = -1;
						closeCode = -1;

                  return FastMessage(output, warning);
					}
				}
				#endregion

				#region worldlist
            if (command.Command == "exworldlist")//Regex.IsMatch(chunk.Message, @"^\s*/exworldlist\s*$"))
				{
					string output = "List of all worlds:\n-------------------------------------";

					foreach (WorldInstance world in worlds)
					{
						if (world.CanPlay)
							output += "\n-World " + world.WorldID + " * " + world.GetOwnedAcres().Count + " acre".Pluralify(world.GetOwnedAcres().Count);
					}

               return StyledMessage(output);
				}
				#endregion

				#region setworld
				//match = Regex.Match(chunk.Message, @"^\s*/exsetworld\s+([0-9]+)\s*$");
            if (command.Command == "exsetworld")//match.Success)
				{
					int setWorld;

               if (!int.TryParse(command.Arguments[0], out setWorld))
                  return FastMessage("This is an invalid world selection", true);
					else if (!worlds.Any(x => x.WorldID == setWorld))
                  return FastMessage("There are no worlds with this ID", true);
               else if (allPlayers[user.UID].PlayingWorld == setWorld)
                  return FastMessage("You're already playing on this world", true);

					WorldInstance world = worlds.FirstOrDefault(x => x.WorldID == setWorld);
					if (!world.CanPlay)
                  return FastMessage("This world is unplayable", true);

               allPlayers[user.UID].PlayingWorld = setWorld;

               if (world.StartGame(user.UID, user.Username))
                  return FastMessage("You've entered World " + setWorld + " for the first time!");
					else
                  return FastMessage("You've switched over to World " + setWorld);
				}
				#endregion

				#region toggle
            match = Regex.Match(command.Command, @"extoggle([^\s]+)");
				if (match.Success)
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               return FastMessage(results.Item1.ToggleOption(user.UID, match.Groups[1].Value));
				}
				#endregion

				#region go
				//match = Regex.Match(chunk.Message, @"^\s*/exgo\s*(.*)\s*$");
            if (command.Command == "exgo")//match.Success)
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

					int chatCoinGet = 0;
               string output = results.Item1.PerformActions(user.UID, command.Arguments[0]/*match.Groups[1].Value*/, out chatCoinGet);
//
//					if (chatCoinGet > 0)
//						StandardCalls_RequestCoinUpdate(chatCoinGet, chunk.Username);

               return StyledMessage(output);
				}
				#endregion

				#region cheat
            if (command.Command == "excheat")//Regex.IsMatch(chunk.Message, @"^\s*/excheat\s*$") && Cheating)
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               results.Item1.Cheat(user.UID);

               return FastMessage("You cheated some resources into existence!");
				}
				#endregion

				#region itemlist
            if (command.Command == "exitemlist")//Regex.IsMatch(chunk.Message, @"^\s*/exitemlist\s*$"))
				{
					string output = "";

					foreach (ItemBlueprint item in ExplorerConstants.Items.AllBlueprints
						.Where(x => x.Key != ExplorerConstants.Items.IDS.ChatCoins).Select(x => x.Value)
						.Where(x => x.CanPickup && x.CanObtain || ExplorerConstants.Items.CraftingRecipes.ContainsKey(x.ID)))
					{
						output += item.ShorthandName + " (" + item.DisplayCharacter + ") - " + item.DisplayName + "\n";
					}

               return StyledMessage(output);
				}
				#endregion

				#region equip
				//match = Regex.Match(chunk.Message, @"^\s*/exequip\s+([a-zA-Z]+)\s*$");
            if (command.Command == "exequip")//match.Success)
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               return FastMessage(results.Item1.EquipItem(user.UID, command.Arguments[0].Trim()));//match.Groups[1].Value.Trim());
				}
				#endregion

				#region craft
				//match = Regex.Match(chunk.Message, @"^\s*/excraft\s+([^0-9]+)\s*([0-9]*)\s*$");
            if (command.Command == "excraft")//match.Success)
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

					int amount;

               if (!int.TryParse(/*match.Groups[2].Value*/command.Arguments[1], out amount))
						amount = 1;

               return FastMessage(results.Item1.CraftItem(user.UID, command.Arguments[0].Trim()/*match.Groups[1].Value.Trim()*/, amount));
				}
				#endregion

				#region craftlist
            if (command.Command == "excraftlist")//Regex.IsMatch(chunk.Message, @"^\s*/excraftlist\s*$"))
				{
					string output = "";
					foreach (var craftRecipe in ExplorerConstants.Items.CraftingRecipes.OrderBy(x => x.Value.Values.Sum()))
					{
						output += ExplorerConstants.Items.AllBlueprints[craftRecipe.Key].ShorthandName + " - ";

						foreach (var craftIngredient in craftRecipe.Value)
						{
							output += ExplorerConstants.Items.AllBlueprints[craftIngredient.Key].ShorthandName + "*" + craftIngredient.Value + " ";
						}

						output += "\n";
					}

               return StyledMessage(output);
				}
				#endregion

				#region map
				//match = Regex.Match(chunk.Message, @"^\s*/exmap\s+([0-9]+)\s*$");
            if (command.Command == "exmap")//match.Success)
				{
               //return FastMessage("Not supported right now!", true);

					int worldID;

               if (!int.TryParse(command.Arguments[0], out worldID))
                  return FastMessage("That's not a valid number", true);

					if (!worlds.Any(x => x.WorldID == worldID))
                  return FastMessage("There's no world with this ID", true);

					try
					{
						SaveWorldImage(worlds.FirstOrDefault(x => x.WorldID == worldID));
					}
               catch (Exception e)
					{
                  return FastMessage("An error occurred while generating the map image! Please report this error to an admin." +
                     "\nError: " + e, true);
					}

               return FastMessage("World " + worldID + " map: " + GetOption<string>("mapLink") + "/world" + worldID + ".png");
				}
				#endregion

				#region top
            if (command.Command == "extop")//Regex.IsMatch(chunk.Message, @"^\s*/extop\s*$"))
				{
					List<string> scores = SortedModuleItems(users);
					//List<Tuple<string, int>> scores = allPlayers.Select(x => Tuple.Create(x.Key, worlds.Sum(y => y.GetScore(x.Key)))).ToList();
					string output = "The top explorers are:";

					for (int i = 0; i < 5; i++)
						if (i < scores.Count())
							output += "\n" + (i + 1) + ": " + scores[i];

               return FastMessage(output);
				}
				#endregion

				#region close
            if (command.Command == "exclose")//Regex.IsMatch(chunk.Message, @"^\s*/exclose\s*$"))
				{
					List<string> scores = SortedModuleItems(users);
					//List<Tuple<string, int>> scores = allPlayers.Select(x => Tuple.Create(x.Key, worlds.Sum(y => y.GetScore(x.Key)))).ToList();
               int index = scores.FindIndex(x => x.Contains(user.Username + " "));

					string output = "Your exploration competitors are:";

					for (int i = index - 2; i <= index + 2; i++)
						if (i < scores.Count() && i >= 0)
							output += "\n" + (i + 1) + ": " + scores[i];

               return FastMessage(output);
				}
				#endregion

				#region respawn
            if (command.Command == "exrespawn")//Regex.IsMatch(chunk.Message, @"^\s*/exrespawn\s*$"))
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               return StyledMessage(results.Item1.Respawn(user.UID));
				}
				#endregion

				#region myacres
            if (command.Command == "exmyacres")//Regex.IsMatch(chunk.Message, @"^\s*/exmyacres\s*$"))
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               return FastMessage(results.Item1.PlayerAcres(user.UID));
				}
				#endregion

				#region items
            if (command.Command == "exitems")//Regex.IsMatch(chunk.Message, @"^\s*/exitems\s*$"))
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               return StyledMessage(results.Item1.PlayerItems(user.UID));
				}
				#endregion

            #region storage
            if (command.Command == "exstorage")//Regex.IsMatch(chunk.Message, @"^\s*/exitems\s*$"))
            {
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               return StyledMessage(results.Item1.PlayerStorage(user.UID));
            }
            #endregion

            #region store
            //match = Regex.Match(chunk.Message, @"^\s*/excraft\s+([^0-9]+)\s*([0-9]*)\s*$");
            if (command.Command == "exstore")//match.Success)
            {
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               int amount;

               if (!int.TryParse(/*match.Groups[2].Value*/command.Arguments[1], out amount))
                  amount = int.MaxValue;

               return FastMessage(results.Item1.StoreItems(user.UID, command.Arguments[0].Trim()/*match.Groups[1].Value.Trim()*/, amount));
            }
            #endregion

            #region store
            //match = Regex.Match(chunk.Message, @"^\s*/excraft\s+([^0-9]+)\s*([0-9]*)\s*$");
            if (command.Command == "extake")//match.Success)
            {
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

               int amount;

               if (!int.TryParse(/*match.Groups[2].Value*/command.Arguments[1], out amount))
                  amount = int.MaxValue;

               return FastMessage(results.Item1.TakeItems(user.UID, command.Arguments[0].Trim()/*match.Groups[1].Value.Trim()*/, amount));
            }
            #endregion

				#region teleport
				//match = Regex.Match(chunk.Message, @"^\s*/exteleport\s+([0-9]+)\s*-\s*([0-9]+)\s*$");
            if (command.Command == "exteleport")//match.Success)
				{
               if (!WorldCommandCheck(user.UID, out results))
                  return FastMessage(results.Item2, true);

					int x, y;
               if (!int.TryParse(command.Arguments[0], out x) || !int.TryParse(command.Arguments[1], out y))
                  return FastMessage("Your acre was formatted incorrectly!", true);

               return StyledMessage(results.Item1.TeleportToTower(user.UID, Tuple.Create(x, y)));
				}
				#endregion
			}
			catch (Exception e)
			{
				return FastMessage("An exception occurred: " + e.Message + ". Please report to an admin\n" +
               "Stack trace: \n" + e.StackTrace, true);
			}

         return new List<JSONObject>();
		}
 public List<JSONObject> StyledMessage(string message)//, bool warning = false)
 {
    ModuleJSONObject output = new ModuleJSONObject();
    output.message = "<span style=\"display: block; font-family:'Courier New', Courier, monospace; line-height: 100%;\">" + message + "</span>";
    output.safe = false;
    return new List<JSONObject> { output };
 }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject moduleOutput = new ModuleJSONObject();
         VoteBallot ballot;
         string error = "";
         string output = "";
         long pollNumber = 0;

         if (!userBallots.ContainsKey(user.UID))
            userBallots.Add(user.UID, new List<VoteBallot>());

         try
         {
            switch(command.Command)
            {
               case "pollcreate":
                  int maxPolls = MaxUserPolls * (user.CanStaffChat ? 5 : 1);
                  if (userBallots[user.UID].Count >= maxPolls)
                     return FastMessage("You've reached the maximum amount of allowed polls (" + maxPolls + ") and cannot post a new one", true);
                  else if (command.ArgumentParts[1].Count > MaxPollChoices)
                     return FastMessage("There are too many choices in your poll! The max is " + MaxPollChoices, true);

                  VoteBallot newBallot = new VoteBallot(command.Arguments[0], new HashSet<string>(command.ArgumentParts[1]));

                  if (newBallot.GetChoices().Count < 2)
                     return FastMessage("Your poll must have at least 2 options", true);

                  userBallots[user.UID].Add(newBallot);
                  moduleOutput.broadcast = true;
                  moduleOutput.message = "A new poll has been created by " + user.Username + ":\n\n" + userBallots[user.UID].Last();
                  outputs.Add(moduleOutput);

                  break;

               case "vote":

                  if(!long.TryParse(command.Arguments[1], out pollNumber))
                     return FastMessage("Your poll number is out of bounds!", true);

                  if(!GetBallot(pollNumber, false, out ballot))
                  {
                     return FastMessage("There is no open poll with ID " + pollNumber);
                  }
                  else
                  {
                     if(!ballot.AddVote(user.UID, command.Arguments[0], out error))
                        return FastMessage(error);
                     else
                        return FastMessage("You voted on this poll: \n\n" + ballot.GetResultString(user.UID));
                  }

               case "polls":
                  
                  output = "The top polls right now: \n";
                  output += PrintList(userBallots.SelectMany(x => x.Value).OrderByDescending(x => x.TotalVotes).Take(10));

                  return FastMessage(output);

               case "poll":
                  
                  if(!long.TryParse(command.Arguments[0], out pollNumber))
                     return FastMessage("Your poll number is out of bounds!", true);

                  if(!GetBallot(pollNumber, true, out ballot))
                  {
                     return FastMessage("There is no open poll with ID " + pollNumber);
                  }
                  else
                  {
                     output = "";
                     bool closed = archivedBallots.SelectMany(x => x.Value).Contains(ballot);

                     if(ballot.DidVote(user.UID) || closed)
                        output = ballot.GetResultString(user.UID);
                     else
                        output = ballot.ToString();

                     int ballotCreator = userBallots.Union(archivedBallots).First(x => x.Value.Contains(ballot)).Key;
                     output += "\nPoll by: " + users[ballotCreator].Username + (closed ? " (closed)" : "");

                     return FastMessage(output);
                  }

               case "pollclose":

                  if(!long.TryParse(command.Arguments[0], out pollNumber))
                     return FastMessage("Your poll number is out of bounds!", true);

                  if(!userBallots[user.UID].Any(x => x.ID == pollNumber))
                     return FastMessage("You don't have any open polls with this ID!");

                  if(!GetBallot(pollNumber, false, out ballot))
                  {
                     return FastMessage("There is no open poll with ID " + pollNumber);
                  }
                  else
                  {
                     if(!archivedBallots.ContainsKey(user.UID))
                        archivedBallots.Add(user.UID, new List<VoteBallot>());

                     archivedBallots[user.UID].Add(ballot);
                     userBallots[user.UID].Remove(ballot);

                     moduleOutput.broadcast = true;
                     moduleOutput.message = "The poll " + ballot.Title + " has just been closed by " + user.Username + ". The results:\n\n" + ballot.GetResultString();
                     outputs.Add(moduleOutput);
                  }

                  break;

               case "pollsearch":

                  List<Tuple<double, VoteBallot>> sortedBallots = new List<Tuple<double, VoteBallot>>();

                  foreach(VoteBallot searchBallot in userBallots.SelectMany(x => x.Value).Union(archivedBallots.SelectMany(x => x.Value)))
                     sortedBallots.Add(Tuple.Create(StringExtensions.StringDifference(command.Arguments[0].ToLower(), searchBallot.Title.ToLower()), searchBallot));

                  output = "These ballots have a similar title: \n";
                  output += PrintList(sortedBallots.OrderBy(x => x.Item1).Select(x => x.Item2).Take(SearchResults));

                  return FastMessage(output);

               case "pollsopen":

                  output = "Your open polls right now are: \n" + PrintList(userBallots[user.UID]);
                  return FastMessage(output);
            }
         }
         catch(Exception e)
         {
            return new List<JSONObject>() { 
               new ModuleJSONObject() { 
                  message = "Something terrible happened in the Vote module: " + e, broadcast = true 
               } };
         }

         return outputs;
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject moduleOutput = new ModuleJSONObject();

         string message = "";
         UserInfo parsedInfo;

         try
         {
            switch(command.Command)
            {
               case "spamscore":
                  moduleOutput.message = "Your spam score is: " + user.SpamScore + ", offense score: " + user.GlobalSpamScore;
                  outputs.Add(moduleOutput);
                  break;

               case "resetserver":
                  int timeout = 5;

                  //Get the real timeout if one was given
                  if (command.Arguments.Count > 0 && !string.IsNullOrWhiteSpace(command.Arguments[0]))
                     timeout = int.Parse(command.Arguments[0]);

                  TimeSpan realTimeout = TimeSpan.FromSeconds(timeout);

                  //Make sure the user can even run such a high command
                  if(!user.ChatControl)
                     return FastMessage("You don't have access to this command!", true); 

                  //Request the reset we wanted
                  ChatRunner.PerformRequest(new SystemRequest(SystemRequests.Reset, realTimeout));

                  moduleOutput.message = user.Username + " is resetting the server in " + StringExtensions.LargestTime(realTimeout);
                  moduleOutput.broadcast = true;
                  outputs.Add(moduleOutput);

                  break;

               case "simulatelock":

                  //Make sure the user can even run such a high command
                  if(!user.ChatControlExtended)
                     return FastMessage("You don't have access to this command!", true); 
                  
                  //Request the lock we wanted
                  ChatRunner.PerformRequest(new SystemRequest(SystemRequests.LockDeath, TimeSpan.FromSeconds(5)));

                  moduleOutput.message = user.Username + " is simulating a server crash. The server WILL be unresponsive if successful";
                  moduleOutput.broadcast = true;
                  outputs.Add(moduleOutput);

                  break;

               case "savemodules":

                  //Make sure the user can even run such a high command
                  if(!user.ChatControlExtended)
                     return FastMessage("You don't have access to this command!", true); 

                  ChatRunner.PerformRequest(new SystemRequest(SystemRequests.SaveModules));

                  moduleOutput.message = user.Username + " is saving all module data. This may cause a small hiccup";
                  moduleOutput.broadcast = true;
                  outputs.Add(moduleOutput);

                  break;

               case "checkserver":
                  
                  //Make sure the user can even run such a high command
                  if(!user.ChatControl)
                     return FastMessage("You don't have access to this command!", true); 

                  message = "Information about the chat server:\n\n";

                  Dictionary<string, List<UserMessageJSONObject>> history = ChatRunner.Server.GetHistory();
                  List<UserMessageJSONObject> messages = ChatRunner.Server.GetMessages();
                  List<LogMessage> logMessages = ChatRunner.Server.Settings.LogProvider.GetMessages();
                  List<LogMessage> logFileBuffer = ChatRunner.Server.Settings.LogProvider.GetFileBuffer();
                  List<Module> modules = ChatRunner.Server.GetModuleListCopy();

                  message += "Rooms: " + history.Keys.Count + "\n";
                  message += "History messages: " + history.Sum(x => x.Value.Count) + "\n";
                  message += "Registered users: " + users.Count + "\n";
                  message += "Total user sessions: " + users.Sum(x => (long)x.Value.SessionCount) + "\n";
                  message += "Stored messages: " + messages.Count + "\n";
                  message += "Stored log: " + logMessages.Count + "\n";
                  message += "Log file buffer: " + logFileBuffer.Count + "\n";
                  message += "Modules loaded: " + string.Join(", ", modules.Select(x => x.GetType().Name)) + " (" + modules.Count + ")\n";
                  message += "Subscribed handlers for extra output: " + this.ExtraCommandHandlerCount + "\n";
                  message += "Registered connections: " + ChatRunner.Server.ConnectedUsers().Count + "\n";

                  using(Process process = Process.GetCurrentProcess())
                  {
                     message += "Virtual Memory: " + (process.PrivateMemorySize64 / 1048576) + "MiB\n";
                     message += "Heap Allocated: " + (GC.GetTotalMemory(true) / 1048576) + "MiB\n"; 
                     message += "Threads: " + process.Threads.Count;
                  }

                  moduleOutput.message = message;
                  outputs.Add(moduleOutput);

                  break;

               case "debuginfo":

                  //if we can't parse the user, use yourself. Otherwise if it parsed but they don't have
                  //access to this command, stop and let them know.
                  if(string.IsNullOrWhiteSpace(command.Arguments[0]) || !GetUserFromArgument(command.Arguments[0], users, out parsedInfo))
                     parsedInfo = user;
                  else if(!user.ChatControl && parsedInfo.UID != user.UID)
                     return FastMessage("You don't have access to this command!", true); 

                  message = parsedInfo.Username + "'s debug information: \n\n";

                  message += "Session ID: " + parsedInfo.LastSessionID + "\n";
                  message += "Open sessions: " + parsedInfo.OpenSessionCount + "\n";
                  message += "Bad sessions: " + parsedInfo.BadSessionCount + "\n";
                  message += "Current session time: " + StringExtensions.LargestTime(parsedInfo.CurrentSessionTime) + "\n";
                  message += "UID: " + parsedInfo.UID + "\n";
                  message += "Active: " + parsedInfo.Active + "\n";
                  message += "Last ping: " + StringExtensions.LargestTime(DateTime.Now - parsedInfo.LastPing) + "\n";
                  message += "Last post: " + StringExtensions.LargestTime(DateTime.Now - parsedInfo.LastPost) + "\n";
                  message += "Last entry: " + StringExtensions.LargestTime(DateTime.Now - parsedInfo.LastJoin) + "\n";
                  message += "Staff chat: " + parsedInfo.CanStaffChat + "\n";
                  message += "Global chat: " + parsedInfo.CanGlobalChat + "\n";
                  message += "Chat control: " + parsedInfo.ChatControl + "\n";
                  message += "Chat control extended: " + parsedInfo.ChatControlExtended + "\n";
                  message += "Avatar: " + user.Avatar + "\n";

                  moduleOutput.message = message;
                  outputs.Add(moduleOutput);

                  break;
            }
         }
         catch (Exception e)
         {
            return FastMessage("An error has occurred in the debug module: " + e.Message, true);
         }

         return outputs;
      }
      public void SendMessage(ModuleJSONObject message)
      {
         //List<Chat> recipients = new List<Chat>();
//         List<Chat> CurrentChatters = ConnectedUsers().Select(x => (Chat)x).ToList();
//
//         lock (managerLock)
//         {
//            Log("Enter sendmessage lock", MyExtensions.Logging.LogLevel.Locks);
//            foreach (int user in message.recipients.Distinct())
//            {
//               if (activeChatters.Any(x => x.UID == user))
//                  recipients.Add(activeChatters.First(x => x.UID == user));
//               else
//                  Logger.LogGeneral("Recipient " + user + " in module message was not found", MyExtensions.Logging.LogLevel.Warning);
//            }
//            Log("Leave sendmessage lock", MyExtensions.Logging.LogLevel.Locks);
//         }

         List<Chat> recipients = ConnectedUsers().Select(x => (Chat)x).Where(x => message.recipients.Contains(x.UID)).ToList();

         foreach(Chat recipient in recipients)
            recipient.MySend(message.ToString());
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();

         ModuleJSONObject output = new ModuleJSONObject();

         switch (command.Command)
         {
            case "about":
               output = new ModuleJSONObject();
               BandwidthContainer bandwidth = ChatRunner.Bandwidth; //Chat.GetBandwidth();
               DateTime built = ChatRunner.MyBuildDate();
               DateTime crashed = ChatRunner.LastCrash();
               string crashedString = "never";

               if (crashed.Ticks > 0)
                  crashedString = StringExtensions.LargestTime(DateTime.Now - crashed) + " ago";
                  
               output.message = 
                  "---Build info---\n" +
                  "Version: " + ChatRunner.AssemblyVersion() + "\n" +
                  "Runtime: " + StringExtensions.LargestTime(DateTime.Now - ChatRunner.Startup) + "\n" +
                  "Built " + StringExtensions.LargestTime(DateTime.Now - built) + " ago (" + built.ToString("R") + ")\n" +
                  "---Data usage---\n" +
                  "Outgoing: " + bandwidth.GetTotalBandwidthOutgoing() + " (1h: " + bandwidth.GetHourBandwidthOutgoing() + ")\n" +
                  "Incoming: " + bandwidth.GetTotalBandwidthIncoming() + " (1h: " + bandwidth.GetHourBandwidthIncoming() + ")\n" +
                  "---Websocket---\n" +
                  "Library Version: " + WebSocketServer.Version + "\n" +
                  "Last full crash: " + crashedString;
               outputs.Add(output);
               break;

            case "policy":
               output = new ModuleJSONObject(ChatServer.Policy);
               outputs.Add(output);
               break;

            case "help":
               output = new ModuleJSONObject();
               if (command.Arguments.Count == 0)
               {
                  output.message = "Which module would you like help with?\n";

                  foreach (Module module in ChatRunner.Server.GetModuleListCopy(user.UID))
                     output.message += "\n" + module.Nickname;

                  output.message += "\n\nRerun help command with a module name to see commands for that module";
                  outputs.Add(output);
               }
               else
               {
                  output.message = GetModuleHelp(command.Arguments[0], user);
                  outputs.Add(output);
               }
               break;
            case "helpregex":
               output.message = GetModuleHelp(command.Arguments[0], user, true);
               outputs.Add(output);
               break;
            case "uactest":
               output = new ModuleJSONObject();
               output.message = "User " + command.OriginalArguments[0] + " corrects to " + command.Arguments[0];
               outputs.Add(output);
               break;
         }

         return outputs;
      }
Example #10
0
//      protected override void OnError(ErrorEventArgs e)
//      {
//         if(e.Message != null)
//            Logger.Error("UID: " + uid + " - " + e.Message, "WebSocket");
//         if(e.Exception != null)
//            Logger.Error(e.Exception.ToString(), "WebSocket");
//
//         //OK let's see what happens here
//         //OnClose(null);
//         //base.OnError(e);
//      }

      //I guess this is WHENEVER it receives a message?
      public override void ReceivedMessage(string rawMessage)
      {
         //Log("Got message: " + rawMessage, MyExtensions.Logging.LogLevel.Debug);

         ResponseJSONObject response = new ResponseJSONObject();
         response.result = false;
         dynamic json = new Object();
         string type = "";

         //You HAVE to do this, even though it seems pointless. Users need to show up as banned immediately.
         ThisUser.PullInfoFromQueryPage();

         //Before anything else, log the amount of incoming data
         if (!string.IsNullOrEmpty(rawMessage))
         {
            manager.Bandwidth.AddIncoming(rawMessage.Length + HeaderSize);
         }

         //First, just try to parse the JSON they gave us. If it's absolute
         //garbage (or just not JSON), let them know and quit immediately.
         try
         {
            json = JsonConvert.DeserializeObject(rawMessage);
            type = json.type;
            response.from = type;
         }
         catch
         {
            response.errors.Add("Could not parse JSON");
         }

         //If we got a bind message, let's try to authorize this channel.
         if (type == "bind")
         {
            if (uid > 0)
            {
               response.errors.Add("Received another bind message, but you've already been authenticated.");
            }
            else
            {
               try
               {
                  //First, gather information from the JSON. This is so that if
                  //the json is invalid, it will fail as soon as possible
                  string key = (string)json.key;
                  int newUser = (int)json.uid;

                  //Oops, username was invalid
                  if (newUser <= 0)
                  {
                     Log("Tried to bind a bad UID: " + newUser);
                     response.errors.Add("UID was invalid");
                  }
                  else
                  {
                     //List<Chat> removals;
                     string error;

                     if (!manager.CheckAuthentication(newUser, key, out error))  //newUser, key, out removals, out error))
                     {
                        response.errors.Add(error);
                     }
                     else
                     {
                        //Before we do anything, remove other chatting sessions
                        foreach (Chat removeChat in GetAllUsers().Select(x => (Chat)x).Where(x => x.UID == newUser && x != this))
                           removeChat.CloseSelf();
                        
                           //Sessions.CloseSession(removeChat.ID);
                     
                        uid = newUser;

                        //BEFORE adding, broadcast the "whatever has entered the chat" message
                        if (ThisUser.ShowMessages)
                        {
                           manager.Broadcast(QuickParams(ChatTags.Join), new SystemMessageJSONObject(),
                              new List<Chat> { this });
                        }
                        
                        MySend(NewSystemMessageFromTag(QuickParams(ChatTags.Welcome)).ToString());
                        ChatTags enterSpamWarning = ThisUser.JoinSpam();

                        if (enterSpamWarning != ChatTags.None)
                           MySend(NewWarningFromTag(QuickParams(enterSpamWarning)).ToString());

                        //BEFORE sending out the user list, we need to perform onPing so that it looks like this user is active
                        sessionID = ThisUser.PerformOnChatEnter();
//                        if (!ThisUser.PerformOnChatEnter())
//                           Log("Invalid session entry. Sessions may be broken", LogLevel.Warning);

                        manager.BroadcastUserList();

                        Log("Authentication complete: UID " + uid + " maps to username " + ThisUser.Username +
                           (ThisUser.CanStaffChat ? "(staff)" : ""));
                        response.result = true;

                        List<JSONObject> outputs = new List<JSONObject>();
                        Dictionary<int, UserInfo> currentUsers = manager.UsersForModules();

                        //Also do some other crap
                        foreach (Module module in manager.GetModuleListCopy())
                        {
                           if (Monitor.TryEnter(module.Lock, manager.ChatSettings.MaxModuleWait))
                           {
                              try
                              {
                                 outputs.AddRange(module.OnUserJoin(currentUsers[ThisUser.UID], currentUsers));
                              }
                              finally
                              {
                                 Monitor.Exit(module.Lock);
                              }
                           }
                           else
                           {
                              Log("Skipped " + module.ModuleName + " join processing", 
                                 MyExtensions.Logging.LogLevel.Warning);
                           }
                        }

                        OutputMessages(outputs, ThisUser.UID);

                        //Finally, output the "Yo accept dis" thing if they haven't already.
                        if(!ThisUser.AcceptedPolicy)
                        {
                           MessageListJSONObject emptyMessages = new MessageListJSONObject();
                           UserListJSONObject emptyUsers = new UserListJSONObject();
                           ModuleJSONObject policy = new ModuleJSONObject(ChatServer.Policy);
                           ModuleJSONObject accept = new ModuleJSONObject("\nYou must accept this chat policy before " +
                              "using the chat. Type /accept if you accept the chat policy\n");
                           MySend(emptyMessages.ToString(), true);
                           MySend(emptyUsers.ToString(), true);
                           MySend(policy.ToString(), true);
                           MySend(accept.ToString(), true);
                        }
                        else if(ThisUser.ShouldPolicyRemind)
                        {
                           ModuleJSONObject policy = new ModuleJSONObject(ChatServer.Policy);
                           MySend(policy.ToString());
                           ThisUser.PerformOnReminder();
                        }

                        //Now set up the IRC relay. Oh boy, let's hope this works!
                        /*relay = new SimpleIRCRelay(manager.IrcServer, manager.IrcChannel, ThisUser.Username, Logger);
                        relay.ConnectAsync();*/
                        //relay.IRCRelayMessageEvent += OnIrcRelayMessage;
                     }
                  }
               }
               catch
               {
                  response.errors.Add("BIND message was missing fields");
               }
            }
         }
         else if (type == "ping")
         {
            lastPing = DateTime.Now;

            bool active = true;

            try
            {
               active = (bool)json.active;
               //relay.Ping();
            }
            catch (Exception messageError)
            {
               response.errors.Add("Internal server error: " + messageError/*.Message*/);
            }

            ThisUser.PerformOnPing(active);
            UpdateActiveUserList(null, null);
         }
         else if (type == "message")
         {
            try
            {
               //First, gather information from the JSON. This is so that if
               //the json is invalid, it will fail as soon as possible
               string key = json.key;
               string message = System.Security.SecurityElement.Escape((string)json.text);
               string tag = json.tag;

               //These first things don't increase spam score in any way
               if (string.IsNullOrWhiteSpace(message))
               {
                  response.errors.Add("No empty messages please");
               }
               else if (!manager.CheckKey(uid, key))
               {
                  Log("Got invalid key " + key + " from " + UserLogString);
                  response.errors.Add("Your key is invalid");
               }
               else if (!ThisUser.AcceptedPolicy)
               {
                  if(message != "/accept")
                  {
                     response.errors.Add("The only command available right now is /accept");
                  }
                  else
                  {
                     ModuleJSONObject acceptSuccess = new ModuleJSONObject("You have accepted the SmileBASIC Source " +
                        "chat policy. Please use the appropriate chat tab for discussion about SmileBASIC or off-topic " +
                        "subjects!");
                     MySend(acceptSuccess.ToString(), true);

                     Thread.Sleep(2000);
                     ThisUser.AcceptPolicy();
                     ThisUser.PerformOnReminder();
                     response.result = true;

                     MySend(manager.ChatUserList(UID));
                     MySend(manager.ChatMessageList(UID));
                  }
               }
               else if (ThisUser.Blocked)
               {
                  response.errors.Add(NewWarningFromTag(QuickParams(ChatTags.Blocked)).message);
               }
               else if (ThisUser.Banned)
               {
                  response.errors.Add("You are banned from chat for " +
                  StringExtensions.LargestTime(ThisUser.BannedUntil - DateTime.Now));
               }
               else if (tag == "admin" && !ThisUser.CanStaffChat ||
                        tag == manager.ChatSettings.GlobalTag && !ThisUser.CanGlobalChat)
               {
                  response.errors.Add("You can't post messages here. I'm sorry.");
               }
               else if (!manager.ValidTagForUser(UID, tag))
               {
                  response.errors.Add("Your post has an unrecognized tag. Cannot display");
               }
               else
               {
                  Dictionary<int, UserInfo> currentUsers = manager.UsersForModules();
                  List<JSONObject> outputs = new List<JSONObject>();
                  UserMessageJSONObject userMessage = new UserMessageJSONObject(ThisUser, message, tag);
                  UserCommand userCommand;
                  Module commandModule;
                  string commandError = "";

                  //Step 1: parse a possible command. If no command is parsed, no module will be written.
                  if (TryCommandParse(userMessage, out commandModule, out userCommand, out commandError))
                  {
                     Log("Trying to use module " + commandModule.ModuleName + " to process command " + 
                        userCommand.message + " from " + ThisUser.Username, MyExtensions.Logging.LogLevel.SuperDebug);

                     //We found a command. Send it off to the proper module and get the output
                     if (Monitor.TryEnter(commandModule.Lock, manager.ChatSettings.MaxModuleWait))
                     {
                        try
                        {
                           outputs.AddRange(commandModule.ProcessCommand(userCommand, currentUsers[ThisUser.UID], currentUsers));
                        }
                        finally
                        {
                           Monitor.Exit(commandModule.Lock);
                        }
                     }
                     else
                     {
                        response.errors.Add("The chat server is busy and can't process your command right now");
                        userMessage.SetHidden();
                        userMessage.SetUnspammable();
                     }

                     //do not update spam score if command module doesn't want it
                     if (!userCommand.MatchedCommand.ShouldUpdateSpamScore)
                        userMessage.SetUnspammable();

                     //For now, simply capture all commands no matter what.
                     userMessage.SetHidden();
                     //userMessage.SetCommand();

                     Log("Module " + commandModule.ModuleName + " processed command from " + UserLogString, 
                        MyExtensions.Logging.LogLevel.Debug);
                  }
                  else
                  {
                     //If an error was given, add it to our response
                     if (!string.IsNullOrWhiteSpace(commandError))
                     {
                        response.errors.Add("Command error: " + commandError);
                        userMessage.SetHidden();
                        userMessage.SetUnspammable();
                     }
                  }

                  ChatTags warning = manager.AddMessage(userMessage);

                  //Send off on relay
                  /*if(userMessage.Display && userMessage.tag == manager.IrcTag)
                  {
                     if(relay.SendMessage(userMessage.message))
                        Logger.LogGeneral("Sent message on IRC relay!", MyExtensions.Logging.LogLevel.SuperDebug);
                     else
                        Logger.LogGeneral("Couldn't send on IRC relay!", MyExtensions.Logging.LogLevel.SuperDebug);
                  }*/

                  if (warning != ChatTags.None)
                     outputs.Add(NewWarningFromTag(QuickParams(warning)));

                  response.result = response.errors.Count == 0;

                  //Now send out userlist if active status changed
                  UpdateActiveUserList(null, null);

                  //Since we added a new message, we need to broadcast.
                  if (response.result && userMessage.Display)
                     manager.BroadcastMessageList();        //CRASH ALMOST CERTAINLY HAPPENS HERE!!!!!!!###$$$$$!!!!!!!!!!*$*$*$*

                  //Step 2: run regular message through all modules' regular message processor (probably no output?)
                  if(manager.ChatSettings.AcceptedTags.Contains(userMessage.tag))
                  {
                     foreach (Module module in manager.GetModuleListCopy())
                     {
                        if (Monitor.TryEnter(module.Lock, manager.ChatSettings.MaxModuleWait))
                        {
                           try
                           {
                              module.ProcessMessage(userMessage, currentUsers[ThisUser.UID], currentUsers);
                           }
                           finally
                           {
                              Monitor.Exit(module.Lock);
                           }
                        }
                        else
                        {
                           Log("Skipped " + module.ModuleName + " message processing", 
                              MyExtensions.Logging.LogLevel.Warning);
                        }
                     }
                  }

                  //Step 3: run all modules' post processor (no message required)
                  //Is this even necessary? It was necessary before because the bot ran on a timer. Without a timer,
                  //each module can just specify that it wants to do things at random points with its own timer.

                  //Step 4: iterate over returned messages and send them out appropriately
                  OutputMessages(outputs, ThisUser.UID, tag);
                  //end of regular message processing
               }
            }
            catch (Exception messageError)
            {
               response.errors.Add("Internal server error: " + messageError/*.Message*/);
               //response.errors.Add("Message was missing fields");
            }
         }
//         else if (type == "createroom")
//         {
//            try
//            {
//               List<int> users = json.users.ToObject<List<int>>();
//               string error;
//
//               if(!manager.CreatePMRoom(new HashSet<int>(users), ThisUser.UID, out error))
//               {
//                  response.errors.Add(error);
//               }
//               else
//               {
//                  MySend((new SystemMessageJSONObject("You created a chat room for " + string.Join(", ", users.Select(x => manager.GetUser(x).Username)))).ToString());
//                  manager.BroadcastUserList();
//               }
//            }
//            catch
//            {
//               response.errors.Add("Could not parse PM creation room message");
//            }
//         }
         else if (type == "request")
         {
            try
            {
               string wanted = json.request;

               if(wanted == "userList")
               {
                  MySend(manager.ChatUserList(UID));
                  response.result = true;
               }
               else if (wanted == "messageList")
               {
                  MySend(manager.ChatMessageList(UID));
                  response.result = true;
               }
               else
               {
                  response.errors.Add("Invalid request field");
               }
            }
            catch
            {
               response.errors.Add("Request was missing fields");
            }
         }

         //Send the "OK" message back.
         MySend(response.ToString(), true);
      }
      public void SendMessage(ModuleJSONObject message)
      {
         List<Chat> recipients = ConnectedUsers().Select(x => (Chat)x).Where(x => message.recipients.Contains(x.UID)).ToList();

         foreach(Chat recipient in recipients)
            recipient.MySend(message.ToString());
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         try
         {
   			//Match match;
            string cmd = command.Command;
            int myUID = user.UID;

            UserInfo player;
            ModuleJSONObject mainOutput = new ModuleJSONObject();
            List<JSONObject> outputs = new List<JSONObject>();

            TryRegister(user.UID);

   			//Top collectors
   			#region topcollectors
            if (cmd == "cgametop")
   			{
   				var topCollectors = collectors.ToList().OrderByDescending(x => x.Value.Score()).ToList();
   				string output = "The top collectors are:";

   				for (int i = 0; i < 5; i++)
   					if (i < topCollectors.Count())
   						output += "\n" + (i + 1) + ": " + QuickInfo(users[topCollectors[i].Key]);

               return QuickQuit(output);
   			}
   			#endregion

   			//Close collectors
   			#region closecollectors
            else if (cmd == "cgameclose")
   			{
   				var topCollectors = collectors.ToList().OrderByDescending(x => x.Value.Score()).ToList();
               int index = topCollectors.FindIndex(x => x.Key == myUID);

               string output = "Your competitors are:";

   				for (int i = index - 2; i <= index + 2; i++)
   					if (i < topCollectors.Count() && i >= 0)
   						output += "\n" + (i + 1) + ": " + QuickInfo(users[topCollectors[i].Key]);

               return QuickQuit(output);
   			}
   			#endregion

   			//Collect your daily coins
   			#region coinrestock
            else if (cmd == "cgamerestock")
   			{
   				if (collectors[myUID].RestockCoins(DateTime.Now))
   				{
                  return QuickQuit("You received " + collectors[myUID].RestockCoinsAmount() + " coins for the " +
                     CollectionManager.RestockHours + "-hour reset. You now have " + collectors[myUID].Coins + " coins");
   				}
   				else
   				{
   					return QuickQuit("You've already received your coins for this " + CollectionManager.RestockHours + " hour period.\n" +
                     "Please wait another " + StringExtensions.LargestTime(collectors[myUID].RestockWait));
   				}
   			}
   			#endregion

   			//Try to rank up
   			#region rankup
            else if (cmd == "cgamerankup")
   			{
   				if (collectors[myUID].RankUp())
   				{
                  string output = "You've ranked up! Your inventory and journal have been cleared, and you are now rank " + collectors[myUID].Stars;
   //                    if (!StandardCalls_RequestCoinUpdate(RankupCoins, username))
   //                        output += Module.SendMessageBreak + "Unfortunately, the ChatCoin module seems to be malfunctioning, so you will not receive coins";

                  mainOutput.broadcast = true;
                  mainOutput.message = "Hey, " + user.Username + " has become rank " + collectors[myUID].Stars + "(" + collectors[myUID].StarString() + ") in the cgame!";

                  outputs = QuickQuit(output);
                  outputs.Add(mainOutput);
                  return outputs;

                  //return QuickQuit(output);
   				}
   				else
   				{
                  return QuickQuit("You can't rank up just yet. Complete your journal first!");
   				}
   			}
   			#endregion

   			//View your collection
   			#region mycollection
            else if (cmd == "cgamestock")
   			{
               return QuickQuit("-=Your collection=-\n" + collectors[myUID].Statistics(false, JournalStyle), false, false);
   			}
            else if (cmd == "cgamestockm")
            {
               return QuickQuit("-=Your collection=-\n" + collectors[myUID].Statistics(true, JournalStyle), false, false);
            }
   			#endregion

   			//View your top items
   			#region mytopitems
            else if (cmd == "cgametopitems")
   			{
   				var topItems = collectors[myUID].Inventory.AllItemCounts().ToList().OrderByDescending(x => x.Value).ToList();
   				string output = "The top items in your inventory are: ";

   				for (int i = 0; i < 5; i++)
   					if (i < topItems.Count)
   						output += "\n" + CollectionSymbols.GetPointAndSymbol(topItems[i].Key) + " - " + topItems[i].Value;

               return QuickQuit(output);
   			}
   			#endregion

   			//Get multiple items
   			#region drawitems
   			//match = Regex.Match(message, @"^\s*/cgamedraw\s*([0-9]+)?\s*([xX][0-9]+)?\s*$");
            else if (cmd == "cgamedraw")
   			{
   				int multiplier = 1;
   				int amount;

   				//Oops, bad amount number
   				if (!int.TryParse(command.Arguments[0], out amount))
                  return QuickQuit("The amount of items to draw is invalid", true);
               else if (amount <= 0 || amount > MaxWithdraw)
                  return QuickQuit("You can't withdraw that many items!", true);

               //We had a multiplier!
               if(command.Arguments.Count == 2)
               {
                  if (!int.TryParse(command.Arguments[1].ToLower().Replace("x", ""), out multiplier))
                     return QuickQuit("The multiplier is invalid", true);
                  else if (multiplier > CollectionManager.MaxMultiplier || multiplier < CollectionManager.MinMultiplier)
                     return QuickQuit("Your multiplier is out of range. Use values from " + 
                        CollectionManager.MinMultiplier + "-" + CollectionManager.MaxMultiplier, true);
               }

   				int cost = CollectionManager.LotteryCost * multiplier * amount;

   				//Oops, you don't have enough coins
   				if (collectors[myUID].Coins < cost)
                  return QuickQuit("You don't have enough coins. Drawing " + amount + " item".Pluralify(amount) + " with a multiplier of X" + multiplier + " costs " + cost + " coins.");

   				//Generate a list of items (based on the given amount)
   				List<SpecialPoint> items = new List<SpecialPoint>();
   				List<SpecialPoint> newItems = new List<SpecialPoint>();
   				for (int i = 0; i < amount; i++)
   				{
   					items.Add(generator.DrawItemForPlayer(collectors[myUID], multiplier));
   					if (!collectors[myUID].Journal.HasItem(items[i]))
   						newItems.Add(items[i]);

   					//Make the player "buy" the item
   					collectors[myUID].BuyItem(items[i], cost / amount);
   				}

   				string output = "You spent " + cost + " coins on item".Pluralify(items.Count) + ": ";
   				int newItemCount = 0;

   				//Go through each item and buy it/produce output
   				foreach (SpecialPoint drawnItem in items)
   				{
   					//Determine if the item is new. Update values if so
   					bool newItem = false;

   					if (newItems.Contains(drawnItem))
   					{
   						newItem = true;
   						newItems.RemoveAll(x => x == drawnItem);
   						newItemCount++;
   					}

   					//Update output
   					output += CollectionSymbols.GetPointAndSymbol(drawnItem) + (newItem ? "**" : "") + ", ";
   				}

   				output += ListTerminator + " (" + newItemCount + " new)";

               return QuickQuit(output.Replace(", " + ListTerminator, ""));
   			}
   			#endregion

   			//See the chance of getting a new item
   			#region drawchance
   			//match = Regex.Match(message, @"^\s*/cgamechance\s*([xX][0-9]+)?\s*$");
            else if (cmd == "cgamechance")
   			{
   				int multiplier = 1;

               //Oops, bad multiplier
               if (!int.TryParse(command.Arguments[0].ToLower().Replace("x", ""), out multiplier))
                  return QuickQuit("The multiplier is invalid!", true);
   				else if (multiplier > CollectionManager.MaxMultiplier || multiplier < CollectionManager.MinMultiplier)
                  return QuickQuit("Your multiplier is out of range. Use a value between " + 
                     CollectionManager.MinMultiplier + "-" + CollectionManager.MaxMultiplier, true);

   				//Oops, make sure we're not already using the chance calculator thread
   				if (chanceSimulator.IsBusy)
                  return QuickQuit("The chance calculator is busy right now.");
               //Calculate the chance on a background thread
   				else
   					chanceSimulator.RunWorkerAsync(Tuple.Create(myUID, multiplier));

               return new List<JSONObject>();
   			}
   			#endregion

   			//How much do you have of item (given)
   			#region amount
   			//match = Regex.Match(message, @"^\s*/cgameamount\s+([a-zA-Z][0-9])\s*$");
            else if (cmd == "cgameamount")
   			{
               SpecialPoint point = SpecialPoint.Parse(command.Arguments[0]);

   				if (!CollectionManager.ValidPoint(point))
                  return QuickQuit("That item is out of range.", true);

               return QuickQuit("You have " + collectors[myUID].Inventory[point] + " of item " + CollectionSymbols.GetPointAndSymbol(point));
   			}
   			#endregion

   			//Compare your inventory to another's
   			#region compare
   			//match = Regex.Match(message, @"^\s*/cgamecompare\s+(.+)\s*$");
            else if (cmd == "cgamecompare")
            {
               //Generic error (shouldn't happen, but just in case!)
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Oops, the user entered the wrong user
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               if (player.UID == myUID)
                  return QuickQuit("You can't compare with yourself!", true);

   				//Get the unique journal entries
   				List<SpecialPoint> myUnique, theirUnique;
               GetUnique(myUID, player.UID, out myUnique, out theirUnique);

   				string output = "Note: Only the top 5 are shown, so there may be more.\nOnly items that you actually have in your inventory are shown\n-Your unique items are: ";

   				//List out items that only you have
   				for (int i = 0; i < 5; i++)
   					if (i < myUnique.Count)
   						output += CollectionSymbols.GetPointAndSymbol(myUnique[i]) + " ";

               output += "\n-" + player.Username + " has these unique items: ";

   				//List out items that only the other guy has
   				for (int i = 0; i < 5; i++)
   					if (i < theirUnique.Count)
   						output += CollectionSymbols.GetPointAndSymbol(theirUnique[i]) + " ";

               return QuickQuit(output);
   			}
   			#endregion

   			//Perform a quicktrade with the given user
   			#region quicktrade
   			//match = Regex.Match(message, @"^\s*/cgamequicktrade\s+(.+)\s*$");
            else if (cmd == "cgamequicktrade")
   			{
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }
                  
   				//Oops, there is another offer still standing
               try
               {
      				if (offer.OfferStanding)
                     return QuickQuit(OfferStanding(offer, users));
               }
               catch
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, can't trade with yourself!
               if (myUID == player.UID)
                  return QuickQuit("You can't trade with yourself!", true);

   				//Get the unique journal entries
   				List<SpecialPoint> myUnique, theirUnique;
               GetUnique(myUID, player.UID, out myUnique, out theirUnique);

   				//Oops, can't have a nice trade because one of you doesn't have any unique items
   				int tradeItems = Math.Min(myUnique.Count, theirUnique.Count);
   				if (tradeItems == 0)
                  return QuickQuit("There cannot be an even trade between you and " + player.Username);

   				myUnique = myUnique.Take(tradeItems).ToList();
   				theirUnique = theirUnique.Take(tradeItems).ToList();

               offer.CreateTrade(myUID, player.UID, myUnique, theirUnique);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " wants to perform a quick-trade with you.\nOffered item".Pluralify(myUnique.Count) + ": "
                  + PrintPointList(myUnique) + "\nDesired item".Pluralify(theirUnique.Count) + ": " + PrintPointList(theirUnique) + "\nYou have " + offer.OfferTimeout
                  + " seconds to decide (/cgameaccept or /cgamedecline). Note that you two will get the items in your journal, but the item is used up in the process."
                  + " You will not get the item in your inventory";

               outputs.Add(playerOutput);

               mainOutput.message = "Your trade of " + PrintPointList(myUnique) + " for " + PrintPointList(theirUnique) + " with " + player.Username +
                  " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Sell to player
   			#region sellPlayer
            else if (cmd == "cgamesell")
   			{
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if(player.UID == myUID)
                  return QuickQuit("You can't sell to yourself!", true);

   				//Set up some initial info
   				List<SpecialPoint> offeredItems;
   				int desiredCoins;

   				//Oops, there is another offer still standing
   				if (offer.OfferStanding)
                  return QuickQuit(OfferStanding(offer, users));

   				//Ugh, bad values
               if (!int.TryParse(command.Arguments[2], out desiredCoins))
                  return QuickQuit("Your sell amount is out of bounds", true);

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, the other user does not have that many coins
               if (collectors[player.UID].Coins < desiredCoins)
                  return QuickQuit("You don't have " + desiredCoins + " coin".Pluralify(desiredCoins) + " to spend.");

   				//Try to extract the points to sell
   				try
   				{
                  offeredItems = ExtractPoints(command.Arguments[0], user);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				//Create the sale
               offer.CreateSale(myUID, player.UID, desiredCoins, offeredItems);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " has offered to sell you " + PrintPointList(offer.SellerItems) + " for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + ".\nYou have " +
                  offer.OfferTimeout + " seconds to decide (/cgameaccept or /cgamedecline). Note that you will get the item in your journal, but NOT your inventory.";

               outputs.Add(playerOutput);

               mainOutput.message = "Your offer of " + PrintPointList(offer.SellerItems) + " for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + 
                  " with " + player.Username + " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Buy from player
   			#region buyPlayer
            else if (cmd == "cgamebuy")
   			{
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Set up some initial info
   				List<SpecialPoint> desiredItems;
   				int offeredCoins;

   				//Oops, there is another offer still standing
   				if (offer.OfferStanding)
                  return QuickQuit(OfferStanding(offer, users), true);

   				//Ugh, bad values
               if (!int.TryParse(command.Arguments[2], out offeredCoins))
                  return QuickQuit("Your buy amount is out of bounds", true);

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, this user does not have that many coins
               if (collectors[myUID].Coins < offeredCoins)
                  return QuickQuit("You do not have " + offeredCoins + " coin".Pluralify(offeredCoins) + " to spend.");

   				try
   				{
                  desiredItems = ExtractPoints(command.Arguments[0], player);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				//Create the offer
               offer.CreatePurchase(player.UID, myUID, offeredCoins, desiredItems);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " has offered to buy " + PrintPointList(offer.SellerItems) + " from you for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + ".\nYou have " +
                  offer.OfferTimeout + " seconds to decide (/cgameaccept or /cgamedecline). ";

               outputs.Add(playerOutput);

               mainOutput.message = "Your offer of " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + " for " + PrintPointList(offer.SellerItems) + 
                  " with " + player.Username + " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Give away an item
   			#region give
            else if (cmd == "cgamegive")
   			{
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Set up some initial info
   				//string potentialReceiver = match.Groups[3].Value;
   				List<SpecialPoint> points;

   				try
   				{
                  points = ExtractPoints(command.Arguments[0], user);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				//Oops, the user entered the wrong player
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               PerformGive(myUID, player.UID, points);

               ModuleJSONObject playerMessage = new ModuleJSONObject();
               playerMessage.recipients.Add(player.UID);
               playerMessage.message = user.Username + " has given you " + PrintPointList(points) + " for free! How nice!";
               outputs.Add(playerMessage);

               mainOutput.message = "You've given away " + PrintPointList(points) + " to " + player.Username + ". You're nice!";
               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Trade items with another player
   			#region trade
            else if (cmd == "cgametrade")
   			{
   				//Get the other user
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Oops, there is another offer still standing
   				if (offer.OfferStanding)
                  return QuickQuit(OfferStanding(offer, users));

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, can't trade with yourself!
               if (player.UID == myUID)
                  return QuickQuit("You can't trade with yourself!");

   				//Get the points you want to give to the other user
   				List<SpecialPoint> myPoints;
   				List<SpecialPoint> theirPoints;

   				try
   				{
   					myPoints = ExtractPoints(command.Arguments[0], user);
                  theirPoints = ExtractPoints(command.Arguments[2], player);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message);
   				}

               offer.CreateTrade(myUID, player.UID, myPoints, theirPoints);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " wants to perform a trade with you.\nOffered item".Pluralify(myPoints.Count) + ": "
                  + PrintPointList(myPoints) + "\nDesired item".Pluralify(theirPoints.Count) + ": " + PrintPointList(theirPoints) + "\nYou have " + offer.OfferTimeout
                  + " seconds to decide (/cgameaccept or /cgamedecline). Note that you two will get the items in your journal, but the item is used up in the process."
                  + " You will not get the item in your inventory";

               outputs.Add(playerOutput);

               mainOutput.message = "Your trade of " + PrintPointList(myPoints) + " for " + PrintPointList(theirPoints) + " with " + player.Username +
                  " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}

   			#endregion

   			//Sell all of the given items
   			#region sellall
   			//match = Regex.Match(message, @"^\s*/cgamesellall\s+(([a-zA-Z][0-9]\s*)+)\s*$");
            else if (cmd == "cgameshopsell")
   			{
   				//Build up the list of captured points
   				List<SpecialPoint> points;

   				try
   				{
                  points = ExtractPoints(command.Arguments[0], user);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				int totalAmount = 0;

   				//Now for the actual selling
   				foreach (SpecialPoint point in points)
   				{
   					//Get the total amount of that item we can sell
   					int pointAmount = collectors[myUID].Inventory[point];
   					//totalAmount += pointAmount;

   					//Sell it all
   					for (int i = 0; i < pointAmount; i++)
                  {
                     int sellPrice = CollectionManager.SellPrice(point, collectors[myUID]);
                     collectors[myUID].SellItem(point, sellPrice);
                     totalAmount += sellPrice;
                  }
   				}

               return QuickQuit("You sold all " + PrintPointList(points) + " for " + totalAmount + " coins.");
   			}
   			#endregion

   			//Sell ALL of the items
   			#region sellallall
            if (cmd == "cgameshopsellall") //Regex.IsMatch(message, @"^\s*/cgamesellallall\s*$"))
   			{
   				int totalAmount = 0;

   				//Now for the actual selling
   				foreach (SpecialPoint point in collectors[myUID].Inventory.AllItemCounts().Where(x => x.Value > 0).Select(x => x.Key).ToList())
   				{
   					//Get the total amount of that item we can sell
   					int pointAmount = collectors[myUID].Inventory[point];
   					//totalAmount += pointAmount;

   					//Sell it all
   					for (int i = 0; i < pointAmount; i++)
                  {
                     int sellPrice = CollectionManager.SellPrice(point, collectors[myUID]);
                     collectors[myUID].SellItem(point, sellPrice);
                     totalAmount += sellPrice;
                  }
   				}

               return QuickQuit("You sold everything for " + totalAmount + " coins.");
   			}
   			#endregion

   			//Accept a selling offer
   			#region acceptoffer
            else if (cmd == "cgameaccept") //Regex.IsMatch(message, @"^\s*/cgameaccept\s*$"))
            {
   				//Pfft, the sale isn't for you!
   				if (!offer.OfferStanding || myUID != offer.Accepter)
                  return QuickQuit("There are no offers for you right now");

   				string output = "";

   				if (offer.Type == CollectionOffer.OfferType.Buy || offer.Type == CollectionOffer.OfferType.Sell)
   				{
   					//This variable is used so that only the first item in the sale exchanges money. After that, they're basically a trade
   					bool firstItem = true;

   					foreach (SpecialPoint sellItem in offer.SellerItems)
   					{
   						//Complete the sale
   						collectors[offer.Seller].SellItem(sellItem, (firstItem ? offer.BuyerCoins : 0));
   						collectors[offer.Buyer].BuyItem(sellItem, (firstItem ? offer.BuyerCoins : 0));
   						collectors[offer.Buyer].Inventory.UnobtainItem(sellItem);

   						firstItem = false;
   					}

   					output = users[offer.Buyer].Username + " has bought a journal entry for " + PrintPointList(offer.SellerItems) + " from " + users[offer.Seller].Username + " for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + ".";
   				}
   				else if (offer.Type == CollectionOffer.OfferType.Trade)
   				{
   					output = users[offer.Offerer].Username + " gave these items to " + users[offer.Accepter].Username + ": \n";

   					//Perform the trade
   					foreach (SpecialPoint item in offer.OffererItems)
   					{
   						output += CollectionSymbols.GetPointAndSymbol(item) + " ";
   						collectors[offer.Offerer].SellItem(item, 0);
   						collectors[offer.Accepter].BuyItem(item, 0);
   						collectors[offer.Accepter].Inventory.UnobtainItem(item);
   					}
   					output += "\nAnd got these items in return: \n";
   					foreach (SpecialPoint item in offer.AccepterItems)
   					{
   						output += CollectionSymbols.GetPointAndSymbol(item) + " ";
   						collectors[offer.Accepter].SellItem(item, 0);
   						collectors[offer.Offerer].BuyItem(item, 0);
   						collectors[offer.Offerer].Inventory.UnobtainItem(item);
   					}
   				}

               mainOutput.message = output;
               mainOutput.recipients.Add(offer.Accepter);
               mainOutput.recipients.Add(offer.Offerer);
               outputs.Add(mainOutput);

   				//Reset the sell information
   				offer.Reset();

   				//We done yo.
   				return outputs;
   			}
   			#endregion

   			//Decline a selling offer
   			#region declineoffer
            else if (cmd == "cgamedecline")//Regex.IsMatch(message, @"^\s*/cgamedecline\s*$"))
   			{
   				//Pfft, the sale isn't for you!
   				if (!offer.OfferStanding || myUID != offer.Accepter)
                  return QuickQuit("There are no offers for you right now");

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.message = user.Username + " has declined the offer";
               playerOutput.recipients.Add(offer.Offerer);
               outputs.Add(playerOutput);

               mainOutput.message = "You have declined the offer";
               outputs.Add(mainOutput);
   				
   				offer.Reset();

   				return outputs;
   			}
   			#endregion

   			//View your forever statistics (like total coins, total items collected, etc.
   			#region cgameforeverstats
            else if (cmd == "cgamestats")//Regex.IsMatch(message, @"^\s*/cgamestats\s*$"))
   			{
   				if (collectors[myUID].Stars < 1)
                  return QuickQuit("You must be at least rank 1 to see your permanent collection game stats");

               return QuickQuit(collectors[myUID].ForeverStats());
   			}
   			#endregion

   			#region cgamequery
            //match = Regex.Match(message, @"^\s*/cgamequery\s+(([a-zA-Z][0-9]\s*)+)\s*$");
            if (cmd == "cgamequery")//match.Success)
   			{
               List<SpecialPoint> points;

               try
   				{
                  points = ExtractPoints(command.Arguments[0]).Distinct().ToList();
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

               return QuickQuit(GetQuery(points, users));
   			}

            if(cmd == "cgamequeryall")
            {
               List<SpecialPoint> points = collectors[myUID].Journal.Unobtained();

               return QuickQuit(GetQuery(points, users));
            }
   			#endregion

   			#region cgamelove
   			//match = Regex.Match(message, @"^\s*/cgamelove\s+(.+)\s*$");
            else if (cmd == "cgamelove") //match.Success)
   			{
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               return PerformLove(user, player);
   			}
   			#endregion

            #region cgameaddlove
            //match = Regex.Match(message, @"^\s*/cgameaddlove\s+(.+)\s*$");
            else if(cmd == "cgameaddlove")//match.Success)
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               if (myUID == player.UID)
                  return QuickQuit("You can't add yourself to the love list!", true);

               if (collectors[myUID].GetLovers().Count >= MaxLovers)
                  return QuickQuit("You're already at your love limit (" + MaxLovers + ")");

               if (!collectors[myUID].AddLover(player.UID))
                  return QuickQuit(player.Username + " is already in your love list!");

               return QuickQuit("You've added " + player.Username + " to your love list <3");
            }
            #endregion

            #region cgameremovelove
            //match = Regex.Match(message, @"^\s*/cgameremovelove\s+(.+)\s*$");
            else if (cmd == "cgameremovelove")//match.Success)
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if (!collectors[myUID].RemoveLover(player.UID))
                  return QuickQuit(player.Username + " is not in your love list!");

               return QuickQuit("You've removed " + player.Username + " from your love list </3");
            }
            #endregion

            #region cgamelovelist
            else if (cmd == "cgamelovelist")//Regex.IsMatch(message, @"^\s*/cgamelovelist\s*$"))
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               List<int> lovers = collectors[myUID].GetLovers();

               if(lovers.Count == 0)
               return QuickQuit("There is nobody on your love list ='(");

               string output = "These are all the people on your love list:";

               foreach (int lover in lovers)
                  if(users.ContainsKey(lover))
                     output += "\n* " + users[lover].Username;

               return QuickQuit(output);
            }
            #endregion

            #region cgameloveall
            if (cmd == "cgameloveall")//Regex.IsMatch(message, @"^\s*/cgameloveall\s*$"))
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               List<Tuple<int, int>> giveCounts = new List<Tuple<int,int>>();
               List<int> lovers = collectors[myUID].GetLovers().Where(x => collectors.ContainsKey(x) && users.ContainsKey(x)).ToList();

               if (lovers.Count == 0)
                  return QuickQuit("There is nobody on your love list ='(");

               foreach (int lover in lovers)
               {
                  List<SpecialPoint> myUnique, theirUnique;
                  GetUnique(myUID, lover, out myUnique, out theirUnique);
                  giveCounts.Add(Tuple.Create(lover, myUnique.Count));
               }

               if(giveCounts.Any(x => x.Item2 == 0))
               {
                  mainOutput.message = "Love-all skipped: " + string.Join(", ", giveCounts.Where(x => x.Item2 == 0).Select(x => users[x.Item1].Username)) + "\n";
                  outputs.Add(mainOutput);
               }

               foreach (int lover in giveCounts.Where(x => x.Item2 != 0).OrderBy(x => x.Item2).Select(x => x.Item1))
                  outputs.AddRange(PerformLove(user, users[lover]));

               return outputs;
            }
            #endregion

            if(cmd == "cgamecheckjealousy")
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;

               var canSteal = GetJealousy(myUID);

               return QuickQuit("If you tap into the darkness in your twisted heart now, you'll get " + canSteal.Count + " item".Pluralify(canSteal.Count) +
                  " plus " + JealousyCoins * users.Count(x => x.Value.LoggedIn) + " coins (1000 coins for each logged in user)");
            }

            if(cmd == "cgamejealousy")
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;

               Dictionary<SpecialPoint, int> gonnaSteal = GetJealousy(myUID);
               HashSet<int> usersToInform = new HashSet<int>();

               if(gonnaSteal.Count == 0)
                  return QuickQuit("Your jealousy couldn't change the fact that nobody had any items you needed");

               foreach(KeyValuePair<SpecialPoint, int> steal in gonnaSteal)
               {
                  PerformGive(steal.Value, myUID, new List<SpecialPoint> { steal.Key });
                  usersToInform.Add(steal.Value);
               }

               double multiplier = 0.5 + 1.5 * collectors.ToList().OrderBy(x => x.Value.Score()).Select(x => x.Key).ToList().IndexOf(myUID) / (double)collectors.Count;
               collectors[myUID].JealousyPerformed(gonnaSteal.Count, multiplier);

               foreach(int userID in usersToInform)
               {
                  ModuleJSONObject userOutput = new ModuleJSONObject();
                  int itemCount = gonnaSteal.Count(x => x.Value == userID);
                  userOutput.message = user.Username + " took " + itemCount + " item".Pluralify(itemCount) + " from you!";
                  userOutput.recipients.Add(userID);
                  outputs.Add(userOutput);
               }

               int getCoins = JealousyCoins * users.Count(x => x.Value.LoggedIn);
               collectors[myUID].GetCoins(getCoins);

               //WarningJSONObject warnOutput = new WarningJSONObject();
               mainOutput.message = "In a fit of jealousy, " + user.Username + " used the darkness in their twisted heart to " +
                  "generate " + getCoins + " coins and steal " + gonnaSteal.Count + " item".Pluralify(gonnaSteal.Count) + 
                  " from unsuspecting users. 8(>_<)8";
               mainOutput.broadcast = true;
               outputs.Add(mainOutput);

               return outputs;
            }

            if(cmd == "cgamehug")
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;

               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               if(myUID == player.UID)
                  return QuickQuit("You can't hug yourself!", true);

               if(CollectionManager.PerformHug(collectors, myUID, player.UID, JealousyTime))
               {
                  ModuleJSONObject playerOutput = new ModuleJSONObject();
                  playerOutput.recipients.Add(player.UID);
                  playerOutput.message = "You were hugged by " + user.Username + ". The darkness in your heart receded a bit!";
                  mainOutput.message = "You gave " + player.Username + " a hug and cheered them up a bit!";
                  outputs.Add(mainOutput);
                  outputs.Add(playerOutput);
                  return outputs;
               }
               else
               {
                  ModuleJSONObject playerOutput = new ModuleJSONObject();
                  playerOutput.recipients.Add(player.UID);
                  playerOutput.message = "You were hugged by " + user.Username + ". " + HugSayings[localRandom.Next(HugSayings.Count)];
                  mainOutput.message = "You gave " + player.Username + " a hug. What a nice person!";
                  outputs.Add(mainOutput);
                  outputs.Add(playerOutput);
                  return outputs;
               }
            }
         }
         catch(Exception e)
         {
            ModuleJSONObject error = new ModuleJSONObject();
            error.broadcast = true;
            error.message = "The CGAME module has encountered an unknown error. Please tell staff\n\n" +
            "Error message: " + e.ToString();

            return new List<JSONObject> { error };
         }

         return new List<JSONObject>();
		}
 public List<JSONObject> QuickQuit(string message, bool warning = false, bool safe = true)
 {
    if (warning)
    {
       WarningJSONObject output = new WarningJSONObject();
       output.message = message;
       return new List<JSONObject> { output };
    }
    else
    {
       ModuleJSONObject output = new ModuleJSONObject();
       output.message = message;
       output.safe = safe;
       return new List<JSONObject> { output };
    }
 }
      public List<JSONObject> PerformLove(UserInfo user, UserInfo player)
      {
         List<JSONObject> outputs = new List<JSONObject>();

         if (CheckIfJealous(player.UID, out outputs))
            return QuickQuit("The intense dark aura surrounding " + player.Username + " prevents you from loving them :'(");
         
         //Oops, the user entered the wrong player
         if (!collectors.ContainsKey(player.UID))
            return QuickQuit(NotAPlayer(player));

         List<SpecialPoint> myUnique, theirUnique;
         GetUnique(user.UID, player.UID, out myUnique, out theirUnique);

         if (myUnique.Count == 0)
            return QuickQuit("It was a nice gesture, but you don't have any unique items to give to " + player.Username + ".");

         PerformGive(user.UID, player.UID, myUnique);
         collectors[user.UID].LoveGiven(myUnique.Count);
         collectors[player.UID].GotLoved(myUnique.Count);

         ModuleJSONObject playerOutput = new ModuleJSONObject();
         playerOutput.recipients.Add(player.UID);
         playerOutput.message = user.Username + " generously gave you " + myUnique.Count + " item".Pluralify(myUnique.Count) + 
            " - " + PrintPointList(myUnique);
         outputs.Add(playerOutput);

         ModuleJSONObject mainOutput = new ModuleJSONObject();
         mainOutput.message = "You generously gave " + myUnique.Count + " item".Pluralify(myUnique.Count) + 
            " to " + player.Username + " - " + PrintPointList(myUnique);
         outputs.Add(mainOutput);

         return outputs;
      }