PRoConEvents.MULTIbalancer.CommandToLog C# (CSharp) Method

CommandToLog() private method

private CommandToLog ( string cmd ) : void
cmd string
return void
        private void CommandToLog(string cmd)
        {
            try {
            Match m = null;
            String msg = String.Empty;
            ConsoleDump("Command: " + cmd);

            if (Regex.Match(cmd, @"^bad\s+tags?", RegexOptions.IgnoreCase).Success) {
            List<String> failures = new List<String>();
            lock (fKnownPlayers) {
                foreach (String name in fKnownPlayers.Keys) {
                    PlayerModel p = fKnownPlayers[name];
                    if (p.Role != ROLE_PLAYER)
                        continue;

                    double joinedMinutesAgo = GetPlayerJoinedTimeSpan(p).TotalMinutes;
                    double enabledForMinutes = DateTime.Now.Subtract(fEnabledTimestamp).TotalMinutes;
                    if ((enabledForMinutes > MinutesAfterJoining)
                    && (joinedMinutesAgo > MinutesAfterJoining)
                    && (!p.TagVerified || p.TagFetchStatus.State == FetchState.Failed || p.TagFetchStatus.State == FetchState.Aborted)) {
                        failures.Add(name);
                    }
                }
            }
            if (failures.Count == 0) {
                ConsoleDump("^bNo clan tag fetch failures to report");
            } else {
                String tmp = String.Join(", ", failures.ToArray());
                // Limit string to less than 1000
                if (tmp.Length > 1000) {
                    tmp = tmp.Substring(0, 1000) + " ...";
                }
                tmp = tmp + " (" + failures.Count + " total)";
                ConsoleDump("^bUnable to fetch clan tags for: " + tmp);
                int aborted = 0;
                int failed = 0;
                foreach (String pn in failures) {
                    PlayerModel p = GetPlayer(pn);
                    if (p == null) continue;
                    if (p.TagFetchStatus.State == FetchState.Aborted) ++aborted;
                    if (p.TagFetchStatus.State == FetchState.Failed) ++failed;
                }
                ConsoleDump("^bClan tag fetches aborted: " + aborted);
                ConsoleDump("^bClan tag fetches failed: " + failed);
            }
            return;
            }

            if (Regex.Match(cmd, @"^bad\s+stats?", RegexOptions.IgnoreCase).Success) {
            List<String> failures = new List<String>();
            lock (fKnownPlayers) {
                foreach (String name in fKnownPlayers.Keys) {
                    PlayerModel p = fKnownPlayers[name];
                    if (p.Role != ROLE_PLAYER)
                        continue;

                    double joinedMinutesAgo = GetPlayerJoinedTimeSpan(p).TotalMinutes;
                    double enabledForMinutes = DateTime.Now.Subtract(fEnabledTimestamp).TotalMinutes;
                    if ((enabledForMinutes > MinutesAfterJoining)
                    && (joinedMinutesAgo > MinutesAfterJoining)
                    && !p.StatsVerified
                    && (p.StatsFetchStatus.State == FetchState.Failed || p.StatsFetchStatus.State == FetchState.Requesting)) {
                        failures.Add(name);
                    }
                }
            }
            if (failures.Count == 0) {
                ConsoleDump("^bNo stats fetch failures to report");
            } else {
                String tmp = String.Join(", ", failures.ToArray());
                // Limit string to less than 1000
                if (tmp.Length > 1000) {
                    tmp = tmp.Substring(0, 1000) + " ...";
                }
                tmp = tmp + " (" + failures.Count + " total)";
                ConsoleDump("^bUnable to fetch stats for: " + tmp);
                int aborted = 0;
                int failed = 0;
                foreach (String pn in failures) {
                    PlayerModel p = GetPlayer(pn);
                    if (p == null) continue;
                    if (p.TagFetchStatus.State == FetchState.Aborted) ++aborted;
                    if (p.TagFetchStatus.State == FetchState.Failed) ++failed;
                }
                ConsoleDump("^bClan tag fetches aborted: " + aborted);
                ConsoleDump("^bClan tag fetches failed: " + failed);
            }
            return;
            }

            if (Regex.Match(cmd, @"^delay", RegexOptions.IgnoreCase).Success) {
            if (fTotalRoundEndingRounds < 1) {
                ConsoleDump("Not enough rounds timed to make a recommendation yet");
                return;
            }
            double total = (fTotalRoundEndingSeconds/fTotalRoundEndingRounds); // total amount of time between rounds
            double backoff = (TotalPlayerCount() / 15) * 5; // scrambler needs about 5 seconds per 15 players
            backoff = Math.Max(5, backoff);
            double advice = total - backoff;
            advice = Math.Max(((fGameVersion == GameVersion.BFH) ? 10 : 50), advice); // never less than 50 seconds (10 for BFH)
            ConsoleDump("Recommended scrambler delay, based on " + fTotalRoundEndingRounds + " rounds, is " + advice.ToString("F0") + " seconds");
            return;
            }

            m = Regex.Match(cmd, @"^gen\s+((?:cs|cl|ctf|gm|r|sqdm|sr|s|tdm|u|dom|ob|sob|def|crl|crs|bm|hs|hot|bh)|[1234569])", RegexOptions.IgnoreCase);
            if (m.Success) {
            String what = m.Groups[1].Value;
            int section = 8;
            if (!Int32.TryParse(what, out section)) section = 8;

            List<CPluginVariable> vars = GetDisplayPluginVariables();

            String sm = section.ToString() + " -";
            if (section == 8) {
                switch (what) {
                    case "cs":
                        if (fGameVersion != GameVersion.BF3)
                            sm = "for Conquest Small";
                        else
                            sm = "for Conq Small, Dom, Scav";
                        break;
                    case "cl": sm = "for Conquest Large"; break;
                    case "ctf": sm = "for CTF"; break;
                    case "gm": sm = "for Gun Master"; break;
                    case "r": sm = "for Rush"; break;
                    case "sqdm": sm = "for Squad Deathmatch"; break;
                    case "sr": sm = "for Squad Rush"; break;
                    case "s": sm = "for Superiority"; break;
                    case "tdm": sm = "for Team Deathmatch"; break;
                    case "u": sm = "for Unknown or New Mode"; break;
                    case "def": sm = "for Defuse"; break; //bf4
                    case "dom": sm = "for Domination"; break; // bf4
                    case "ob": sm = "for Obliteration"; break; // bf4
                    case "sob": sm = "for Squad Obliteration"; break; // bf4
                    case "crl": sm = "for NS Carrier Large"; break; // bf4
                    case "crs": sm = "for NS Carrier Small"; break; // bf4
                    case "bm": sm = "for Blood Money"; break; // bfh
                    case "hs": sm = "for Heist"; break; // bfh
                    case "hot": sm = "for Hotwire"; break; //bfh
                    case "bh": sm = "for Bounty Hunter"; break; //bfh
                    default: ConsoleDump("Unknown mode: " + what); return;
                }
            }

            foreach (CPluginVariable var in vars) {
                if (section == 8) {
                    if (var.Name.Contains(sm)) {
                        ConsoleDump(var.Name + ": " + var.Value);
                    }
                } else {
                    if (var.Name.Contains(sm)) {
                        ConsoleDump(var.Name + ": " + var.Value);
                    }
                }
            }
            return;
            }

            if (Regex.Match(cmd, @"^histogram", RegexOptions.IgnoreCase).Success) {
            if (fTicketLossHistogram.Total < 1) return;
            List<String> graph = fTicketLossHistogram.Log(60);
            foreach (String line in graph) {
                ConsoleDump(line);
            }
            return;
            }

            if (Regex.Match(cmd, @"^lists", RegexOptions.IgnoreCase).Success) {
            ConsoleDump("Whitelist(" + fSettingWhitelist.Count + "):");
            foreach (String item in fSettingWhitelist) {
                ConsoleDump(item);
            }
            ConsoleDump(" ");
            ConsoleDump("Friends List(" + fFriends.Keys.Count +"):");
            foreach (int k in fFriends.Keys) {
                ConsoleDump(k.ToString() + ": " + String.Join(", ", fFriends[k].ToArray()));
            }
            ConsoleDump(" ");
            ConsoleDump("Disperse Evenly List(" + fSettingDisperseEvenlyList.Count + "):");
            foreach (String item in fSettingDisperseEvenlyList) {
                ConsoleDump(item);
            }
            ConsoleDump(" ");
            for (int i = 1; i <= 4; ++i) { // 1 to 4 teams
                if (fDispersalGroups[i].Count > 0) {
                    msg = "Dispersal Group " + i + " (" + fDispersalGroups[i].Count + "): " + String.Join(", ", fDispersalGroups[i].ToArray());
                    ConsoleDump(msg);
                }
            }
            ConsoleDump(" ");
            msg = "Group assignments: ";
            for (int i = 1; i <= 4; ++i) { // 1 to 4 teams
                msg = msg + fGroupAssignments[i];
                if (i < 4) msg = msg + "/";
            }
            ConsoleDump(msg);
            return;
            }

            if (Regex.Match(cmd, @"^modes", RegexOptions.IgnoreCase).Success) {
            List<String> modeList = GetSimplifiedModes();
            ConsoleDump("modes(" + modeList.Count + "):");
            foreach (String mode in modeList) {
                ConsoleDump(mode);
            }
            return;
            }

            if (Regex.Match(cmd, @"^moved", RegexOptions.IgnoreCase).Success) {
            lock (fKnownPlayers) {
                ConsoleDump("^bMoved by " + GetPluginName() + ":");
                foreach (String name in fKnownPlayers.Keys) {
                    PlayerModel p = fKnownPlayers[name];
                    if (p.Role != ROLE_PLAYER)
                        continue;
                    if ((p.MovesByMBTotal + p.MovesByMBRound) < 1) continue;
                    String minsAgo = "(reset)";
                    String interval = "(never)";
                    if (p.MovedByMBTimestamp != DateTime.MinValue) {
                        minsAgo = DateTime.Now.Subtract(p.MovedByMBTimestamp).TotalMinutes.ToString("F0");
                    }
                    lock (p.MovedByMBHistory) {
                        if (p.MovedByMBHistory.Count > 0) {
                            if (p.MovedByMBHistory.Count == 1) {
                                interval = "(first)";
                            } else {
                                int last = p.MovedByMBHistory.Count - 1;
                                interval = p.MovedByMBHistory[last].Subtract(p.MovedByMBHistory[last-1]).TotalMinutes.ToString("F0") + " minutes apart";
                            }
                        }
                    }
                    ConsoleDump("^b" + p.FullName + "^n was moved " + p.MovesByMBRound + " times this round, " + (p.MovesByMBTotal + p.MovesByMBRound) + " total, the last was " + interval + " and " +  minsAgo + " minutes ago");
                }
                ConsoleDump(" ");
                ConsoleDump("^bMoved by someone or something else:");
                foreach (String name in fKnownPlayers.Keys) {
                    PlayerModel p = fKnownPlayers[name];
                    if (p.Role != ROLE_PLAYER)
                        continue;
                    if (p.MovesRound > 0) {
                        ConsoleDump("^b" + p.FullName + "^n was moved " + p.MovesRound + " times this round, " + (p.MovesTotal + p.MovesRound) + " total");
                    }
                }
                ConsoleDump(" ");
            }
            return;
            }

            if (Regex.Match(cmd, @"^rage", RegexOptions.IgnoreCase).Success) {
            ConsoleDump("Rage stats: " + fGrandRageQuits + " rage of " + fGrandTotalQuits + " total, this round " + fRageQuits + " rage of " + fTotalQuits + " total");
            return;
            }

            if (Regex.Match(cmd, @"^refetch", RegexOptions.IgnoreCase).Success) {
            List<String> fetch = new List<String>();
            lock (fAllPlayers) {
                foreach (String name in fAllPlayers) {
                    PlayerModel p = GetPlayer(name);
                    if (p == null) continue;
                    /*
                    if (!p.TagVerified) {
                        fetch.Add(name);
                        continue;
                    }
                    */
                    if ((p.TagFetchStatus.State == FetchState.InQueue || p.TagFetchStatus.State == FetchState.Requesting)
                        && (p.StatsFetchStatus.State == FetchState.InQueue || p.StatsFetchStatus.State == FetchState.Requesting)) continue;
                    fetch.Add(name);
                }
            }

            if (fetch.Count == 0) {
                ConsoleDump("No active players need info, nothing to refetch!");
                return;
            }

            ConsoleDump("^bRefetching Battlelog info for " + fetch.Count + " players");

            foreach (String name in fetch) {
                PlayerModel p = GetPlayer(name);
                p.TagFetchStatus.State = FetchState.New;
                p.StatsFetchStatus.State = FetchState.New;
                p.TagVerified = false;
                AddPlayerFetch(name);
            }
            return;
            }

            if (Regex.Match(cmd, @"^refresh", RegexOptions.IgnoreCase).Success) {
            fRefreshCommand = true;
            ConsoleDump("Player models will be revalidated on next listPlayers event");
            ScheduleListPlayers(1);
            return;
            }

            if (Regex.Match(cmd, @"^reset settings", RegexOptions.IgnoreCase).Success) {
            ConsoleDump("^8^bRESETTING ALL PLUGIN SETTINGS (except Whitelist and Dispersal list) TO DEFAULT!");
            ResetSettings();
            return;
            }

            if (Regex.Match(cmd, @"^scramble[d]?", RegexOptions.IgnoreCase).Success) {
            if (fDebugScramblerBefore[0].Count == 0
              || fDebugScramblerBefore[1].Count == 0
              || fDebugScramblerAfter[0].Count == 0
              || fDebugScramblerAfter[1].Count == 0) {
                ConsoleDump("No scrambler data available");
                return;
            }
            ConsoleDump("===== BEFORE =====");
            ListSideBySide(fDebugScramblerBefore[0], fDebugScramblerBefore[1], false, (KeepSquadsTogether || KeepClanTagsInSameTeam));
            ConsoleDump("===== AFTER =====");
            ListSideBySide(fDebugScramblerAfter[0], fDebugScramblerAfter[1], false, (KeepSquadsTogether || KeepClanTagsInSameTeam));
            if (KeepSquadsTogether) {
                ConsoleDump(" ");
                // After scramble, compare squads: use both after teams to account for cross-team moves
                CompareSquads(1, 1, fDebugScramblerBefore[0], fDebugScramblerAfter[0], 2, fDebugScramblerAfter[1], false);
                CompareSquads(2, 2, fDebugScramblerBefore[1], fDebugScramblerAfter[1], 1, fDebugScramblerAfter[0], false);
            }
            if (fDebugScramblerStartRound[0].Count > 0 && fDebugScramblerStartRound[1].Count > 0) {
                ConsoleDump("===== START OF ROUND =====");
                ListSideBySide(fDebugScramblerStartRound[0], fDebugScramblerStartRound[1], false, (KeepSquadsTogether || KeepClanTagsInSameTeam));
                if (KeepSquadsTogether) {
                    ConsoleDump(" ");
                    // After team swaps, compare squads
                    CompareSquads(2, 1, fDebugScramblerAfter[1], fDebugScramblerStartRound[0], 2, fDebugScramblerStartRound[1], true);
                    CompareSquads(1, 2, fDebugScramblerAfter[0], fDebugScramblerStartRound[1], 1, fDebugScramblerStartRound[0], true);
                }
            }
            ConsoleDump("===== END =====");
            return;
            }

            if (Regex.Match(cmd, @"^size[s]?", RegexOptions.IgnoreCase).Success) {
            int kp = fKnownPlayers.Count;
            int ap = fAllPlayers.Count;
            int old = 0;
            int validTags = 0;
            int commanders = 0;
            int spectators = 0;
            lock (fKnownPlayers) {
                // count player records more than 12 hours old
                foreach (String name in fKnownPlayers.Keys) {
                    PlayerModel p = fKnownPlayers[name];
                    if (DateTime.Now.Subtract(p.LastSeenTimestamp).TotalMinutes > 12*60) {
                        if (!IsKnownPlayer(name)) {
                            ++old;
                        }
                    }
                    if (p.TagVerified) ++validTags;
                    bool playing = false;
                    lock (fAllPlayers) {
                        playing = fAllPlayers.Contains(name);
                    }
                    if (playing) {
                        if (p.Role == ROLE_SPECTATOR)
                            ++spectators;
                        else if (p.Role == ROLE_COMMANDER_MOBILE || p.Role == ROLE_COMMANDER_PC)
                            ++commanders;
                    }
                }
            }
            ConsoleDump("Plugin has been enabled for " + fRoundsEnabled + " rounds");
            ConsoleDump("fKnownPlayers.Count = " + kp + ", not playing = " + (kp-ap) + ", more than 12 hours old = " + old + ", current commanders = " + commanders + ", current spectators = " + spectators);
            ConsoleDump("fPriorityFetchQ.Count = " + PriorityQueueCount() + ", verified tags = " + validTags);
            ConsoleDump("MULTIbalancerUtils.HTML_DOC.Length = " + MULTIbalancerUtils.HTML_DOC.Length);
            return;
            }

            m = Regex.Match(cmd, @"^sort\s+([1-4])\s+(score|spm|kills|kdr|rank|kpm|bspm|bkdr|bkpm)", RegexOptions.IgnoreCase);
            if (m.Success) {
            String teamID = m.Groups[1].Value;
            String propID = m.Groups[2].Value;

            int team = 0;
            if (!Int32.TryParse(teamID, out team) || team < 1 || team > 4) {
                ConsoleDump("Invalid team: " + teamID);
                return;
            }
            List<PlayerModel> fromList = GetTeam(team);
            if (fromList == null || fromList.Count < 3) {
                ConsoleDump("Invalid team or not enough players in team: " + team);
                return;
            }
            switch (propID.ToLower()) {
                case "score":
                    fromList.Sort(DescendingRoundScore);
                    break;
                case "spm":
                    fromList.Sort(DescendingRoundSPM);
                    break;
                case "kills":
                    fromList.Sort(DescendingRoundKills);
                   break;
                case "kdr":
                    fromList.Sort(DescendingRoundKDR);
                    break;
                case "rank":
                    fromList.Sort(DescendingPlayerRank);
                    break;
                case "kpm":
                    fromList.Sort(DescendingRoundKPM);
                    break;
                case "bspm":
                    fromList.Sort(DescendingSPM);
                    break;
                case "bkdr":
                    fromList.Sort(DescendingKDR);
                    break;
                case "bkpm":
                    fromList.Sort(DescendingKPM);
                    break;
                default:
                    fromList.Sort(DescendingRoundScore);
                    break;
            }
            int n = 1;
            foreach (PlayerModel p in fromList) {
                switch (propID.ToLower()) {
                    case "score":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") Score: " + String.Format("{0,6:F0}", p.ScoreRound) + ", ^b" + p.FullName);
                        break;
                    case "spm":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") SPM: " + String.Format("{0,6:F0}", p.SPMRound) + ", ^b" + p.FullName);
                        break;
                    case "kills":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") Kills: " + String.Format("{0,6:F0}", p.KillsRound) + ", ^b" + p.FullName);
                       break;
                    case "kdr":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") KDR: " + String.Format("{0,6:F1}", p.KDRRound) + ", ^b" + p.FullName);
                        break;
                    case "rank":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") Rank: " + String.Format("{0,6:F0}", p.Rank) + ", ^b" + p.FullName);
                        break;
                    case "kpm":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") KPM: " + String.Format("{0,6:F1}", p.KPMRound) + ", ^b" + p.FullName);
                        break;
                    case "bspm":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") bSPM: " + String.Format("{0,6:F0}", ((p.StatsVerified) ? p.SPM : p.SPMRound)) + ", ^b" + p.FullName);
                        break;
                    case "bkdr":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") bKDR: " + String.Format("{0,6:F1}", ((p.StatsVerified) ? p.KDR : p.KDRRound)) + ", ^b" + p.FullName);
                        break;
                    case "bkpm":
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") bKPM: " + String.Format("{0,6:F0}", ((p.StatsVerified) ? p.KPM : p.KPMRound)) + ", ^b" + p.FullName);
                        break;
                    default:
                        ConsoleDump("#" + String.Format("{0,2}", n) + ") Score: " + String.Format("{0,6:F0}", p.ScoreRound) + ", ^b" + p.FullName);
                        break;
                }
                n = n + 1;
            }
            return;
            }

            if (Regex.Match(cmd, @"^status", RegexOptions.IgnoreCase).Success) {
            LogStatus(false, 7);
            return;
            }

            if (Regex.Match(cmd, @"^subscribed", RegexOptions.IgnoreCase).Success) {
            lock (fAllPlayers) {
                foreach (String name in fAllPlayers) {
                    PlayerModel p = GetPlayer(name);
                    if (p != null && p.Subscribed) {
                        ConsoleDump("^b" + p.FullName + "^n is subscribed to all balancer messages in chat");
                    }
                }
            }
            return;
            }

            if (Regex.Match(cmd, @"^tags?", RegexOptions.IgnoreCase).Success) {
            Dictionary<String,List<PlayerModel>> byTag = new Dictionary<String,List<PlayerModel>>();

            lock (fAllPlayers) {
                foreach (String name in fAllPlayers) {
                    PlayerModel player = GetPlayer(name);
                    if (player == null || player.Team < 1 || player.Team > 2) continue;
                    String tag = ExtractTag(player);
                    if (String.IsNullOrEmpty(tag)) continue;
                    if (!byTag.ContainsKey(tag)) {
                        byTag[tag] = new List<PlayerModel>();
                    }
                    byTag[tag].Add(player);
                }
            }

            List<String> tags = new List<String>();
            foreach (String t in byTag.Keys) {
                tags.Add(t);
                byTag[t].Sort(delegate(PlayerModel lhs, PlayerModel rhs) { // ascending by team/squad
                    if (lhs == null && rhs == null) return 0;
                    if (lhs == null) return -1;
                    if (rhs == null) return 1;

                    // by team, then by squad
                    if (lhs.Team < rhs.Team) return -1;
                    if (lhs.Team > rhs.Team) return 1;
                    if (lhs.Team == rhs.Team) {
                        if (lhs.Squad < 1 || rhs.Squad < 1) return 0;
                        if (lhs.Squad < rhs.Squad) return -1;
                        if (lhs.Squad > rhs.Squad) return 1;
                    }
                    return 0;
                });
            }
            tags.Sort();

            foreach (String t in tags) {
                ConsoleDump("Tag [" + t + "]:");
                List<PlayerModel> clan = byTag[t];
                foreach (PlayerModel p in clan) {
                    ConsoleDump(String.Format("        {0}, {1}, {2}",
                        p.Name,
                        GetTeamName(p.Team),
                        GetSquadName(p.Squad)
                    ));
                }
            }
            ConsoleDump(" === END OF TAGS === ");
            return;
            }

            if (Regex.Match(cmd, @"^whitelist", RegexOptions.IgnoreCase).Success) {
            ConsoleDump("Whitelist:");
            String bCodes = String.Empty;
            String uCodes = String.Empty;
            String sCodes = String.Empty;
            String dCodes = String.Empty;
            String rCodes = String.Empty;
            String all =    String.Empty;

            List<String> plist = null;
            lock (fAllPlayers) {
                plist = new List<String>(fAllPlayers);
            }

            foreach (String item in fSettingWhitelist) {
                List<String> tokens = new List<String>(Regex.Split(item, @"\s+"));
                if (tokens.Count < 1) {
                    ConsoleError("tokens.Count < 1!");
                    continue;
                }
                String line = String.Empty;
                for (int i = 0; i < tokens.Count ; ++i) {
                    line = line + tokens[i] + " ";
                }
                ConsoleDump("WL: " + line);
            }

            foreach (String name in plist) {
                try {
                    PlayerModel player = GetPlayer(name);
                    if (player == null) continue;
                    if (CheckWhitelist(player, WL_ALL)) {
                        if (String.IsNullOrEmpty(all)) {
                            all = "    All: " + player.Name;
                        } else {
                            all = all + ", " + player.Name;
                        }
                        continue;
                    }
                    if (CheckWhitelist(player, WL_BALANCE)) {
                        if (String.IsNullOrEmpty(bCodes)) {
                            bCodes = "    Balance only: " + player.Name;
                        } else {
                            bCodes = bCodes + ", " + player.Name;
                        }
                    }
                    if (CheckWhitelist(player, WL_UNSTACK)) {
                        if (String.IsNullOrEmpty(uCodes)) {
                            uCodes = "    Unstack only: " + player.Name;
                        } else {
                            uCodes = uCodes + ", " + player.Name;
                        }
                    }
                    if (CheckWhitelist(player, WL_SWITCH)) {
                        if (String.IsNullOrEmpty(sCodes)) {
                            sCodes = "     Switch only: " + player.Name;
                        } else {
                            sCodes = sCodes + ", " + player.Name;
                        }
                    }
                    if (CheckWhitelist(player, WL_DISPERSE)) {
                        if (String.IsNullOrEmpty(dCodes)) {
                            dCodes = "   Disperse only: " + player.Name;
                        } else {
                            dCodes = dCodes + ", " + player.Name;
                        }
                    }
                    if (CheckWhitelist(player, WL_RANK)) {
                        if (String.IsNullOrEmpty(rCodes)) {
                            rCodes = "       Rank only: " + player.Name;
                        } else {
                            rCodes = rCodes + ", " + player.Name;
                        }
                    }
                } catch (Exception e) {
                    ConsoleException(e);
                }
            }

            if (!String.IsNullOrEmpty(all)) ConsoleDump(all);
            if (!String.IsNullOrEmpty(bCodes)) ConsoleDump(bCodes);
            if (!String.IsNullOrEmpty(uCodes)) ConsoleDump(uCodes);
            if (!String.IsNullOrEmpty(sCodes)) ConsoleDump(sCodes);
            if (!String.IsNullOrEmpty(dCodes)) ConsoleDump(dCodes);
            if (!String.IsNullOrEmpty(rCodes)) ConsoleDump(rCodes);
            return;
            }

            // test BF3 fetch
            Match testF3 = Regex.Match(cmd, @"^test f3 ([^\s]+)", RegexOptions.IgnoreCase);
            if (testF3.Success) {
            int oldLevel = DebugLevel;
            DebugLevel = 7;
            try {
                ConsoleDump("Testing BF3 Clantag fetch:");
                String tn = testF3.Groups[1].Value;
                PlayerModel dummy = GetPlayer(tn);
                if (dummy == null) {
                    ConsoleDump("Player ^b" + tn + "^n seems to have left the server");
                    dummy = new PlayerModel(tn, 1);
                } else {
                    ConsoleDump("Player ^b" + tn + "^n, TagVerified: " + dummy.TagVerified + ", TagFetchStatus: " + dummy.TagFetchStatus.State + ", PersonaId: " + dummy.PersonaId);
                }
                SendBattlelogRequest(dummy.Name, "clanTag", dummy);
                ConsoleDump("Status = " + dummy.TagFetchStatus.State);
                dummy.TagVerified = (dummy.TagFetchStatus.State != FetchState.Failed);
            } catch (Exception e) {
                ConsoleException(e);
            }
            DebugLevel = oldLevel;
            return;
            }

            // test BF4 fetch
            Match testF4 = Regex.Match(cmd, @"^test f4 ([^\s]+)", RegexOptions.IgnoreCase);
            if (testF4.Success) {
            int oldLevel = DebugLevel;
            DebugLevel = 7;
            try {
                ConsoleDump("Testing BF4 Clantag fetch:");
                String tn = testF4.Groups[1].Value;
                PlayerModel dummy = GetPlayer(tn);
                if (dummy == null) {
                    ConsoleDump("Player ^b" + tn + "^n seems to have left the server");
                    dummy = new PlayerModel(tn, 1);
                } else {
                    ConsoleDump("Player ^b" + tn + "^n, TagVerified: " + dummy.TagVerified + ", TagFetchStatus: " + dummy.TagFetchStatus.State + ", PersonaId: " + dummy.PersonaId);
                }
                SendBattlelogRequestBF4(dummy.Name, "clanTag", dummy);
                ConsoleDump("Status = " + dummy.TagFetchStatus.State);
                dummy.TagVerified = (dummy.TagFetchStatus.State != FetchState.Failed);
            } catch (Exception e) {
                ConsoleException(e);
            }
            DebugLevel = oldLevel;
            return;
            }

            // test BFH fetch
            Match testFH = Regex.Match(cmd, @"^test fh ([^\s]+)", RegexOptions.IgnoreCase);
            if (testFH.Success) {
            int oldLevel = DebugLevel;
            DebugLevel = 7;
            try {
                ConsoleDump("Testing BFH Clantag fetch:");
                String tn = testFH.Groups[1].Value;
                PlayerModel dummy = GetPlayer(tn);
                if (dummy == null) {
                    ConsoleDump("Player ^b" + tn + "^n seems to have left the server");
                    dummy = new PlayerModel(tn, 1);
                } else {
                    ConsoleDump("Player ^b" + tn + "^n, TagVerified: " + dummy.TagVerified + ", TagFetchStatus: " + dummy.TagFetchStatus.State + ", PersonaId: " + dummy.PersonaId);
                }
                SendBattlelogRequestBFH(dummy.Name, "clanTag", dummy);
                ConsoleDump("Status = " + dummy.TagFetchStatus.State);
                dummy.TagVerified = (dummy.TagFetchStatus.State != FetchState.Failed);
            } catch (Exception e) {
                ConsoleException(e);
            }
            DebugLevel = oldLevel;
            return;
            }

            // Undocumented command: risky (hide|show)
            Match risky = Regex.Match(cmd, @"^risky (hide|show)", RegexOptions.IgnoreCase);
            if (risky.Success) {
            if (risky.Groups[1].Value == "show") {
                fShowRiskySettings = true;
            } else {
                fShowRiskySettings = false;
            }
            if (fShowRiskySettings) {
                ConsoleDump("Showing risky settings!");
            } else {
                ConsoleDump("Hiding risky settings!");
            }
            return;
            }

            // Undocumented command: test scrambler
            if (Regex.Match(cmd, @"^test scrambler", RegexOptions.IgnoreCase).Success) {
            ConsoleDump("Testing scrambler:");
            ScrambleByCommand(1, true); // log only, winner is always team 1
            return;
            }

            // Undocumented command: test @mb ...
            if (Regex.Match(cmd, @"^test @mb", RegexOptions.IgnoreCase).Success) {
            ConsoleDump("Testing chat command:");
            String tmp = cmd.Replace("test ", String.Empty);
            try {
                fTestMBCommand = true;
                OnGlobalChat("[Plugin]", tmp);
            } catch (Exception) {
                // Do nothing
            } finally {
                fTestMBCommand = false;
            }
            return;
            }

            // Undocumented command: test fast balance
            if (Regex.Match(cmd, @"^test fast", RegexOptions.IgnoreCase).Success) {
            if (!EnableAdminKillForFastBalance) {
                ConsoleDump("Enable Admin Kill For Fast Balance must be True to test, skipping");
                return;
            }
            ConsoleDump("Testing fast balance:");
            if (fTestFastBalance) {
                fTestFastBalance = false;
                ConsoleDump("Deactivated fast balance test");
            } else {
                fTestFastBalance = true;
                FastBalance("Test: ");
            }
            return;
            }

            // Undocumented command: test clan dispersal
            if (Regex.Match(cmd, @"^test clan", RegexOptions.IgnoreCase).Success) {
            PerModeSettings perMode = GetPerModeSettings();
            if (perMode.DisperseEvenlyByClanPlayers == 0) {
                ConsoleDump("per-mode Disperse Evenly By Clan Players must be more than 0 to test, skipping");
                return;
            }
            ConsoleDump("Testing clan dispersal:");
            if (fTestClanDispersal) {
                fTestClanDispersal = false;
                ConsoleDump("Deactivated clan dispersal testing");
            } else {
                fTestClanDispersal = true;
                ConsoleDump("Activated clan dispersal testing");
            }
            return;
            }

            // Undocumented command: generate VBCode from HTML
            if (Regex.Match(cmd, @"^vbcode", RegexOptions.IgnoreCase).Success) {
            String vbCode = MULTIbalancerUtils.ConvertHTMLToVBCode(MULTIbalancerUtils.HTML_DOC);
            ConsoleDump("Converted " + MULTIbalancerUtils.HTML_DOC.Length + " chars of HTML to " + vbCode.Length + " chars of VBCode!");
            try {
                String path = Path.Combine(Directory.GetParent(Application.ExecutablePath).FullName, "vbcode.txt");

                using (FileStream fs = File.Open(path, FileMode.Create)) {
                    Byte[] buffer = new UTF8Encoding(true).GetBytes(vbCode);
                    fs.Write(buffer, 0, buffer.Length);
                    ConsoleDump("Successfully wrote " + path);
                }
            } catch (Exception e) {
                ConsoleException(e);
            }
            return;
            }

            if (Regex.Match(cmd, @"^\s*help", RegexOptions.IgnoreCase).Success || !String.IsNullOrEmpty(cmd)) {
            ConsoleDump("^1^bbad tags^n^0: Examine list of players whose clan tag fetch failed");
            ConsoleDump("^1^bbad stats^n^0: Examine list of players whose stats fetch failed");
            ConsoleDump("^1^bdelay^n^0: Examine recommended scrambler delay time");
            ConsoleDump("^1^bgen^n ^imode^n^0: Generate settings listing for ^imode^n (one of: cs, cl, ctf, gm, r, sqdm, sr, s, tdm, dom, ob, def, crl, crs, bm, hs, hot, bh, u)");
            ConsoleDump("^1^bgen^n ^isection^n^0: Generate settings listing for ^isection^n (1-6,9)");
            ConsoleDump("^1^bhistogram^n^0: Examine a histogram graph of ticket loss ratios");
            ConsoleDump("^1^blists^n^0: Examine all settings that are lists");
            ConsoleDump("^1^bmodes^n^0: Examine the known game modes");
            ConsoleDump("^1^bmoved^n^0: Examine which players were moved, how many times total and how long ago");
            ConsoleDump("^1^brage^n^0: Examine rage quit statistics");
            ConsoleDump("^1^brefetch^n^0: Refetch Battlelog info for all active players");
            ConsoleDump("^1^brefresh^n^0: Force refresh of player list");
            ConsoleDump("^1^breset settings^n^0: Reset all plugin settings to default, except for ^bWhitelist^n and ^bDisperse Evenly List^n");
            ConsoleDump("^1^bscrambled^n^0: Examine list of players before and after last successful scramble");
            ConsoleDump("^1^bsizes^n^0: Examine the sizes of various data structures");
            ConsoleDump("^1^bsort^n ^iteam^n ^itype^n^0: Examine sorted ^iteam^n (1-4) by ^itype^n (one of: score, spm, kills, kdr, rank, kpm, bspm, bkdr, bkpm)");
            ConsoleDump("^1^bstatus^n^0: Examine full status log, as if Debug Level were 7");
            ConsoleDump("^1^bsubscribed^n^0: Examine all players who are subscribed to balancer chat messages");
            ConsoleDump("^1^btags^n^0: Examine list of players sorted by clan tags");
            ConsoleDump("^1^btest f3^n ^iname^n^0: Test BF3 tag fetch");
            ConsoleDump("^1^btest f4^n ^iname^n^0: Test BF4 tag fetch");
            ConsoleDump("^1^bwhitelist^n^0: Examine whitelist combined with reserved slots, by option codes");
            return;
            }

            } catch (Exception e) {
            ConsoleException(e);
            }
        }
MULTIbalancer