// Sort delegate
        public static int DescendingMetricSquad(SquadRoster lhs, SquadRoster rhs)
        {
            if (lhs == null) {
            return ((rhs == null) ? 0 : -1);
            } else if (rhs == null) {
            return ((lhs == null) ? 0 : 1);
            }

            // Dividing by Clan Tag takes precedence, only when both are zero is the metric used
            if (lhs.ClanTagCount > 0 || rhs.ClanTagCount > 0) {
            if (lhs.ClanTagCount < rhs.ClanTagCount) { return 1; }
            if (lhs.ClanTagCount > rhs.ClanTagCount) { return -1; }
            return 0;
            }

            if (lhs.Metric < rhs.Metric) return 1;
            if (lhs.Metric > rhs.Metric) return -1;
            return 0;
        }
        private void ScramblerLoop()
        {
            /*
            Strategy: Scan each team and build filtered team and optionally squad lists.
            The ScrambleBy metric of each item in the pool is calculated. The pool is
            sorted according to the ScrambleBy setting. The best player/squad is assigned
            to the losing team, then the team total is calculated. More strong players/squads
            are added to the losing team until its metric sum is greater than the winning team,
            then players/squads are added to the winning team until it is greater, and so on.
            If at any time a team is full, the remainder of the players/squads are added to
            the other team.

            Finally, each member of the new team is checked and if they need to be moved,
            a move command is issued.  Since this is between rounds, a special move command
            that bypasses all move tracking is used.
            */
            try {
            DateTime last = DateTime.MinValue;
            while (fIsEnabled) {
            double delay = 0;
            DateTime since = DateTime.MinValue;
            bool logOnly = false;

            fWhileScrambling = false;
            lock (fExtrasLock) {
                fExtraNames.Clear();
            }

            lock (fScramblerLock) {
                while (fScramblerLock.MaxDelay == 0) {
                    Monitor.Wait(fScramblerLock);
                    if (!fIsEnabled) return;
                }
                if (fScramblerLock.MaxDelay == -1) {
                    fScramblerLock.MaxDelay = 0;
                    logOnly = true;
                }
                delay = fScramblerLock.MaxDelay;
                since = fScramblerLock.LastUpdate;
                fScramblerLock.MaxDelay = 0;
                fScramblerLock.LastUpdate = DateTime.MinValue;
            }

            if (since == DateTime.MinValue) continue;

            if (!logOnly && last != DateTime.MinValue && DateTime.Now.Subtract(last).TotalMinutes < 3) {
                DebugScrambler("^0Last scramble was less than 5 minutes ago, skipping!");
                continue;
            }

            try {

                PerModeSettings perMode = GetPerModeSettings();

                // wait specified number of seconds
                if (delay > 0) {
                    bool listUpdated = false;
                    while (DateTime.Now.Subtract(since).TotalSeconds < delay) {
                        try {
                            if (!listUpdated && delay - DateTime.Now.Subtract(since).TotalSeconds <= 5) {
                                // update the player list within 5 seconds of the delay expiring
                                listUpdated = true;
                                DebugScrambler("Last chance player list update, account for players who have left");
                                ServerCommand("admin.listPlayers", "all");
                            }
                        } catch (Exception) {}
                        Thread.Sleep(1000); // 1 second
                        if (!fIsEnabled) return;
                    }
                }

                String extra = String.Empty;
                if (DivideBy == DivideByChoices.ClanTag) extra = " [" + ClanTagToDivideBy + "]";
                String kst = String.Empty;
                if (KeepSquadsTogether) kst = ", KeepSquadsTogether";
                String kctiss = String.Empty;
                if (KeepClanTagsInSameTeam) {
                    kctiss = ", KeepClansTagsInSameTeam";
                    if (KeepFriendsInSameTeam) kctiss = kctiss + ", KeepFriendsInSameTeam";
                }
                DebugScrambler("Starting scramble of " + this.TotalPlayerCount + " players, winner was " + GetTeamName(fWinner));
                DebugScrambler("Using (" + ScrambleBy + kst + kctiss + ", DivideBy = " + DivideBy + extra + ")");
                if (!logOnly) last = DateTime.Now;

                // Build a filtered list
                List<String> toScramble = new List<String>();
                //List<String> exempt = new List<String>();
                PlayerModel player = null;

                lock (fAllPlayers) {
                    foreach (String egg in fAllPlayers) {
                        try {
                            player = GetPlayer(egg);
                            if (player == null) continue;

                            // For debugging
                            if (player.Team > 0 && player.Team <= 2) {
                                fDebugScramblerBefore[player.Team-1].Add(player.ClonePlayer());
                            } else continue; // skip joining players

                            // Add this player to list of scramblers
                            toScramble.Add(egg);
                        } catch (Exception e) {
                            if (DebugLevel >= 8) ConsoleException(e);
                        }
                    }

                    // Now that we have captured our master list, handle new joins with care
                    if (toScramble.Count > 0 && !logOnly) fWhileScrambling = true;
                }

                if (toScramble.Count == 0) continue;

                // Build squad tables, clan tables and overall list
                List<SquadRoster> all = new List<SquadRoster>();
                List<PlayerModel> usHaveNoSquad = new List<PlayerModel>();
                List<PlayerModel> ruHaveNoSquad = new List<PlayerModel>();
                List<SquadRoster> usSquadOfOne = new List<SquadRoster>();
                List<SquadRoster> ruSquadOfOne = new List<SquadRoster>();
                Dictionary<int,SquadRoster> squads = new Dictionary<int,SquadRoster>(); // key int is (team * 1000) + squad
                List<PlayerModel> loneWolves = new List<PlayerModel>();
                int key = 0;
                String debugMsg = String.Empty;

                foreach (String egg in toScramble) {
                    try {
                        if (!IsKnownPlayer(egg)) continue; // might have left while we were working
                        player = GetPlayer(egg);
                        if (player == null) continue;
                        if (player.Team < 1) continue; // skip players that are still joining
                        PlayerModel clone = player.ClonePlayer(); // from now on, use a clone
                        if (clone.Squad < 1) {
                            if (clone.Squad == 0) {
                                if (clone.Team == 1) { usHaveNoSquad.Add(clone); }
                                else if (clone.Team == 2) { ruHaveNoSquad.Add(clone); }
                            }
                            continue; // skip players not in a squad
                        }
                        key = 9000; // free pool
                        int squadId = clone.Squad;
                        if (KeepSquadsTogether) {
                            key = (Math.Max(0, clone.Team) * 1000) + Math.Max(0, clone.Squad);
                            if (key < 1000) {
                                loneWolves.Add(clone);
                                continue;
                            } else {
                                DebugScrambler("Keeping ^b" + clone.FullName + "^n together with squad, using key " + key);
                            }
                            AddPlayerToSquadRoster(squads, clone, key, squadId, true);
                        } else if (KeepClanTagsInSameTeam) {
                            String tt = ExtractTag(clone);
                            if (tt == null) tt = String.Empty;
                            int numInSquad = CountMatchingTags(clone, Scope.SameSquad);
                            // Keep players with same clan tag in the same squad
                            //if (numInSquad >= 2) {
                                key = (Math.Max(0, clone.Team) * 1000) + Math.Max(0, clone.Squad); // 0 is okay, makes lone-wolf pool
                                if (String.IsNullOrEmpty(tt) || key < 1000) {
                                    loneWolves.Add(clone);
                                    continue;
                                } else if (numInSquad >= 2) {
                                    DebugScrambler("Keeping ^b" + clone.Name + "^n together with " + numInSquad + " tags [" + tt + "] with squad, using key " + key);
                                }
                            /*
                            } else {
                                loneWolves.Add(clone);
                                continue;
                            */
                            //}
                            AddPlayerToSquadRoster(squads, clone, key, squadId, true);
                        } else {
                            loneWolves.Add(clone);
                        }
                    } catch (Exception e) {
                        if (DebugLevel >= 8) ConsoleException(e);
                    }
                }

                // Add lone wolves to empty squads
                int emptyId = 1;
                SquadRoster home = null;
                bool filling = false;
                foreach (PlayerModel wolf in loneWolves) {
                    bool goback = true;
                    while (goback) {
                        if (!filling) {
                            // Need to find an empty squad
                            key = (wolf.Team * 1000) + emptyId;
                            while (squads.ContainsKey(key)) {
                                emptyId = emptyId + 1;
                                if (emptyId > (SQUAD_NAMES.Length - 1)) break;
                                key = (wolf.Team * 1000) + emptyId;
                            }
                            filling = true;
                        }
                        if (emptyId > (SQUAD_NAMES.Length - 1)) break;
                        if (filling) {
                            // Add wolf to the squad we are filling until full
                            key = (wolf.Team * 1000) + emptyId;
                            home = AddPlayerToSquadRoster(squads, wolf, key, emptyId, false);
                            if (home == null || !home.Roster.Contains(wolf)) {
                                // Full
                                filling = false;
                                continue;
                            } else {
                                // Next wolf
                                DebugScrambler("Lone wolf ^b" + wolf.Name + "^n filled in empty squad " + wolf.Team + "/" + emptyId);
                                goback = false;
                                continue;
                            }
                        }
                    }
                }

                // Sum up the metric for each squad
                foreach (int k in squads.Keys) {
                    SquadRoster sr = squads[k];
                    if (sr.Roster.Count == 1) {
                        if (sr.Roster[0].Team == 1) { usSquadOfOne.Add(sr); }
                        else if (sr.Roster[0].Team == 2) { ruSquadOfOne.Add(sr); }
                    }
                    switch (ScrambleBy) {
                        case DefineStrong.RoundScore:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.ScoreRound;
                            }
                            break;
                        case DefineStrong.RoundSPM:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.SPMRound;
                            }
                            break;
                        case DefineStrong.RoundKills:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.KillsRound;
                            }
                            break;
                        case DefineStrong.RoundKDR:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.KDRRound;
                            }
                            break;
                        case DefineStrong.PlayerRank:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.Rank;
                            }
                            break;
                        case DefineStrong.RoundKPM:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.KPMRound;
                            }
                            break;
                        case DefineStrong.BattlelogSPM:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + ((p.StatsVerified) ? p.SPM : p.SPMRound);
                            }
                            break;
                        case DefineStrong.BattlelogKDR:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + ((p.StatsVerified) ? p.KDR : p.KDRRound);
                            }
                            break;
                        case DefineStrong.BattlelogKPM:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + ((p.StatsVerified) ? p.KPM : p.KPMRound);
                            }
                            break;
                        default:
                            foreach (PlayerModel p in sr.Roster) {
                                sr.Metric = sr.Metric + p.ScoreRound;
                            }
                            break;
                    }

                    String ot = (sr.Roster[0].Team == 1) ? "US" : "RU";
                    DebugScrambler(ot + "/" + SQUAD_NAMES[sr.Squad] + "(" + sr.Roster.Count + ") " + ScrambleBy + ":" + sr.Metric.ToString("F1"));

                    switch (DivideBy) {
                        case DivideByChoices.ClanTag:
                            foreach (PlayerModel p in sr.Roster) {
                                if (!String.IsNullOrEmpty(ClanTagToDivideBy) && ExtractTag(p) == ClanTagToDivideBy) sr.ClanTagCount = sr.ClanTagCount + 1;
                            }
                            debugMsg = "ClanTag[" + ClanTagToDivideBy + "] " + sr.ClanTagCount;
                            break;
                        case DivideByChoices.DispersalGroup: {
                            int[] gCount = new int[3]{0,0,0};
                            foreach (PlayerModel p in sr.Roster) {
                                if (IsDispersal(p, true)) {
                                    if (p.DispersalGroup == 1 || p.DispersalGroup == 2) {
                                        gCount[p.DispersalGroup] = gCount[p.DispersalGroup] + 1;
                                    }
                                }
                            }
                            if (gCount[1] != 0 || gCount[2] != 0) {
                                sr.DispersalGroup = (gCount[1] > gCount[2]) ? 1 : 2;
                            }
                            debugMsg = "Dispersal Group = " + sr.DispersalGroup;
                            break;
                        }
                        case DivideByChoices.None:
                        default:
                            break;
                    }

                    if (DivideBy != DivideByChoices.None) DebugScrambler("Divide " + ot + "/" + SQUAD_NAMES[sr.Squad] + " by " + debugMsg);

                    all.Add(sr);
                }
                squads.Clear();

                if (all.Count == 0) continue;

                // Sort squads
                all.Sort(DescendingMetricSquad);

                DebugScrambler("After sorting:");
                foreach (SquadRoster ds in all) {
                    String oldt = (ds.Roster[0].Team == 1) ? "US" : "RU";
                    DebugScrambler("    " + ScrambleBy + ":" + ds.Metric.ToString("F1") + " " + oldt + "/" + SQUAD_NAMES[ds.Squad]);
                }

                // Prepare the new team lists
                List<PlayerModel> usScrambled = new List<PlayerModel>();
                Dictionary<int,SquadRoster> usSquads = new Dictionary<int,SquadRoster>();
                double usMetric = 0;
                List<PlayerModel> ruScrambled = new List<PlayerModel>();
                Dictionary<int,SquadRoster> ruSquads = new Dictionary<int,SquadRoster>();
                double ruMetric = 0;

                // Dole out squads, keeping metric in balance, starting with the losing team
                List<PlayerModel> target = (fWinner == 0 || fWinner == 1) ? ruScrambled : usScrambled;
                Dictionary<int,SquadRoster> targetSquadTable = (fWinner == 0 || fWinner == 1) ? ruSquads : usSquads;
                int teamMax = MaximumServerSize/2;
                debugMsg = String.Empty;

                // Pre-process DivideBy setting
                if (DivideBy == DivideByChoices.DispersalGroup) {
                    // Skim the dispersal squads off the top
                    List<PlayerModel> localTarget = null;
                    List<SquadRoster> copy = new List<SquadRoster>(all);
                    foreach (SquadRoster disp in copy) {
                        if (disp.DispersalGroup == 1 && usScrambled.Count < teamMax) {
                            localTarget = usScrambled;
                            debugMsg = GetTeamName(1);
                        } else if (disp.DispersalGroup == 2 && ruScrambled.Count < teamMax) {
                            localTarget = ruScrambled;
                            debugMsg = GetTeamName(2);
                        } else {
                            continue;
                        }
                        DebugScrambler("Squad " + SQUAD_NAMES[disp.Squad] + ", Dispersal Group " + disp.DispersalGroup + " to " + debugMsg + " team");
                        AssignSquadToTeam(disp, targetSquadTable, usScrambled, ruScrambled, localTarget);
                        all.Remove(disp);
                    }
                    if (usScrambled == target && target.Count >= teamMax) target = ruScrambled;
                    if (ruScrambled == target && target.Count >= teamMax) target = usScrambled;
                }

                SquadRoster squad = (all.Count > 0) ? all[0] : null;
                List<PlayerModel> opposing = null;
                Dictionary<int,SquadRoster> opposingSquadTable = null;
                do {
                    if (squad == null) break;

                    all.Remove(squad);

                    AssignSquadToTeam(squad, targetSquadTable, usScrambled, ruScrambled, target);

                    // Recalc team metrics
                    SumMetricByTeam(usScrambled, ruScrambled, out usMetric, out ruMetric);
                    if (logOnly || DebugLevel >= 6) DebugScrambler("Updated scrambler metrics " + ScrambleBy + ": US(" + usScrambled.Count + ") = " + usMetric.ToString("F1") + ", RU(" + ruScrambled.Count + ") = " + ruMetric.ToString("F1"));

                    if (usScrambled.Count >= teamMax && ruScrambled.Count >= teamMax) {
                        all.Clear(); // no more room, skip remaining squads
                        break;
                    }

                    if (all.Count == 0) break;

                    // Choose new target team based on metrics
                    if (usScrambled.Count >= teamMax && ruScrambled.Count < teamMax) {
                        target = ruScrambled;
                        targetSquadTable = ruSquads;
                        opposing = usScrambled;
                        squad = all[0];
                        continue; // skip additional checks, no other choice
                    } else if (ruScrambled.Count >= teamMax && usScrambled.Count < teamMax) {
                        target = usScrambled;
                        targetSquadTable = usSquads;
                        opposing = ruScrambled;
                        squad = all[0];
                        continue; // skip additional checks, no other choice
                    } else if (usMetric < ruMetric) {
                        target = usScrambled;
                        targetSquadTable = usSquads;
                        opposing = ruScrambled;
                        debugMsg = "Scrambling to target = " + GetTeamName(1);
                    } else {
                        target = ruScrambled;
                        targetSquadTable = ruSquads;
                        opposing = usScrambled;
                        debugMsg = "Scrambling to target = " + GetTeamName(2);
                    }

                    // Override choice if teams would be too unbalanced by player count
                    if (target.Count > opposing.Count) {
                        // Take a weak squad from the end of the list instead
                        squad = all[all.Count-1];
                        // assign to the opposing team
                        List<PlayerModel> tmp = target;
                        target = opposing;
                        opposing = tmp;
                        if (target == usScrambled) {
                            targetSquadTable = usSquads;
                            debugMsg = "^4REVISED for count target = " + GetTeamName(1);
                        } else {
                            targetSquadTable = ruSquads;
                            debugMsg = "^4REVISED for count target = " + GetTeamName(2);
                        }
                    } else {
                        squad = all[0]; // use strongest squad
                    }

                    if (logOnly || DebugLevel >= 6) {
                        DebugScrambler(" ");
                        DebugScrambler(debugMsg + ", squad " + SQUAD_NAMES[squad.Squad] + " (" + squad.Roster.Count + ")");
                    }

                } while (all.Count > 0);

                if (!fIsEnabled) return;

                // Make sure player counts aren't too out of balance
                if (usScrambled.Count <= teamMax && ruScrambled.Count <= teamMax && Math.Abs(usScrambled.Count - ruScrambled.Count) > 1) {
                    int needed = Math.Abs(usScrambled.Count - ruScrambled.Count)/2;
                    int toTeamId = 0;
                    int targetDispersalGroup = 0;
                    List<PlayerModel> opposingCopy = new List<PlayerModel>();
                    List<PlayerModel> tmpCopy = new List<PlayerModel>();
                    List<PlayerModel> oppHaveNoSquad = null;
                    List<SquadRoster> oppSquadOfOne = null;

                    if (usScrambled.Count < ruScrambled.Count) {
                        target = usScrambled;
                        targetSquadTable = usSquads;
                        targetDispersalGroup = 1;
                        toTeamId = 1;
                        opposing = ruScrambled;
                        opposingSquadTable = ruSquads;
                        oppHaveNoSquad = ruHaveNoSquad;
                        oppSquadOfOne = ruSquadOfOne;
                        debugMsg = GetTeamName(1) + " needs " + needed + " more players";
                    } else {
                        target = ruScrambled;
                        targetSquadTable = ruSquads;
                        targetDispersalGroup = 2;
                        toTeamId = 2;
                        opposing = usScrambled;
                        opposingSquadTable = usSquads;
                        oppHaveNoSquad = usHaveNoSquad;
                        oppSquadOfOne = usSquadOfOne;
                        debugMsg = GetTeamName(2) + " needs " + needed + " more players";
                    }

                    DebugScrambler("Adjusting team sizes, US(" + usScrambled.Count + "/" + fTeam1.Count + ") vs RU(" + ruScrambled.Count + "/" + fTeam2.Count + ") " + debugMsg);

                    // See if we have some new players that joined after we started scrambling
                    List<String> extras = null;
                    lock (fExtrasLock) {
                        if (fExtraNames.Count > 0) {
                            extras = new List<String>();
                            extras.AddRange(fExtraNames);
                        }
                    }
                    if (extras != null) {
                        foreach (String ename in extras) {
                            try {
                                PlayerModel xtra = GetPlayer(ename);
                                if (xtra == null) continue;
                                SquadRoster sr = null;
                                if (targetSquadTable.TryGetValue(xtra.Squad, out sr)) {
                                    if (sr.Roster.Count >= fMaxSquadSize) continue;
                                    sr.Roster.Add(xtra);
                                } else {
                                    sr = new SquadRoster(xtra.Squad);
                                    sr.Roster.Add(xtra);
                                    targetSquadTable[xtra.Squad] = sr;
                                }
                                DebugScrambler("Adding new joining player ^b" + xtra.FullName + "^n to " + TeamName(toTeamId) + " team");
                                target.Add(xtra);
                                lock (fExtrasLock) {
                                    if (fExtraNames.Contains(ename)) fExtraNames.Remove(ename);
                                }
                                --needed;
                                if (needed == 0) break;
                            } catch (Exception e) {
                                ConsoleException(e);
                            }
                        }
                    }

                    // Rearrange opposing team scrambled list so that squad-of-one and have-no-squad players come first
                    tmpCopy.AddRange(opposing);
                    foreach (SquadRoster monoSquad in oppSquadOfOne) {
                        PlayerModel op = monoSquad.Roster[0];
                        opposingCopy.Add(op);
                        tmpCopy.Remove(op);
                    }
                    oppSquadOfOne.Clear();
                    foreach (PlayerModel op in oppHaveNoSquad) {
                        opposingCopy.Add(op);
                        tmpCopy.Remove(op);
                    }
                    oppHaveNoSquad.Clear();
                    // Since team list is sorted, take from the weak end of the team
                    for (int j = tmpCopy.Count - 1; j >= 0; --j) {
                        opposingCopy.Add(tmpCopy[j]);
                    }
                    tmpCopy.Clear();

                    // Move players from opposing team to target team until counts are in balance
                    while (opposing.Count > 0 && (opposing.Count - target.Count) > 1) {
                        PlayerModel filler = null;

                        // Loop through the rearranged copy of opposing team to find a filler player to move to the target team
                        // We use a copy since the original list has to be modified
                        foreach (PlayerModel f in opposingCopy) {
                            if (filler == null) break;

                            // Check to make sure Dispersal isn't violated
                            if (DivideBy == DivideByChoices.DispersalGroup && IsDispersal(filler, true) && filler.DispersalGroup != targetDispersalGroup) {
                                filler = null;
                                continue;
                            }

                            // Make sure player doesn't have clan tag being divided
                            String ft = ExtractTag(filler);
                            if (ft == null) ft = String.Empty;
                            if (DivideBy == DivideByChoices.ClanTag && ft == ClanTagToDivideBy) {
                                filler = null;
                                continue;
                            }

                            // Make sure squad filler is coming from doesn't have clan tags to keep together
                            int cmt = 0;
                            SquadRoster fillerSquad = null;
                            if ((KeepClanTagsInSameTeam || KeepSquadsTogether) && filler.Squad > 0 && opposingSquadTable.TryGetValue(filler.Squad, out fillerSquad) && fillerSquad != null) {
                                foreach (PlayerModel mate in fillerSquad.Roster) {
                                    if (ft == ExtractTag(mate)) ++cmt;
                                }

                                int required = (KeepClanTagsInSameTeam) ? 1 : 2;

                                if (cmt >= required) {
                                    filler = null;
                                    continue;
                                }

                                // TBD same check for friends if KeepFriendsInSameTeam is true
                            }

                            // Otherwise, our candidate filler player is the one to go
                            try {
                                int formerSquad = filler.Squad;
                                AssignFillerToTeam(filler, toTeamId, target, targetSquadTable);
                                opposing.Remove(filler);
                                SquadRoster fromSquad = null;
                                if (formerSquad > 0 && opposingSquadTable.TryGetValue(formerSquad, out fromSquad) && fromSquad != null) {
                                    fromSquad.Roster.Remove(filler);
                                }
                            } catch (Exception e) {
                                ConsoleException(e);
                            }

                            // That's one down, how may more to go? Check in the outer while loop
                            break;
                        }

                        // Check to make sure we found a filler
                        if (filler == null) {
                            DebugScrambler("^8Unable to balance teams for player count, giving up!");
                            break;
                        } else {
                            opposingCopy.Remove(filler);
                        }
                    }
                }

                // Final counts
                DebugScrambler("Final scrambled team counts: US(" + usScrambled.Count + "), RU(" + ruScrambled.Count + ")");

                // Assert that everyone is in their proper team
                foreach (PlayerModel clone in usScrambled) {
                    if (clone.Team != 1) {
                        ConsoleDebug("WARNING: ^b" + clone.FullName + "^n was in " + GetTeamName(clone.Team) + ", correcting to " + GetTeamName(1));
                        clone.Team = 1;
                    }
                }
                foreach (PlayerModel clone in ruScrambled) {
                    if (clone.Team != 2) {
                        ConsoleDebug("WARNING: ^b" + clone.FullName + "^n was in " + GetTeamName(clone.Team) + ", correcting to " + GetTeamName(2));
                        clone.Team = 2;
                    }
                }

                if (!fIsEnabled) return;

                // Remember original squads
                foreach (PlayerModel clone in usScrambled) {
                    if (clone.ScrambledSquad == -1) clone.ScrambledSquad = clone.Squad;
                    if (clone.OriginalSquad == -1) clone.OriginalSquad = clone.Squad;
                }
                foreach (PlayerModel clone in ruScrambled) {
                    if (clone.ScrambledSquad == -1) clone.ScrambledSquad = clone.Squad;
                    if (clone.OriginalSquad == -1) clone.OriginalSquad = clone.Squad;
                }

                // Using live PlayerModels, move players into squad 0 of their unscrambled teams
                // to avoid movement order overflows of squad size
                List<String> unsquaded = new List<String>();
                UnsquadMove(usSquads, ruSquads, logOnly, unsquaded); // uses live players, not clones!

                // Pause 2 seconds to let game server catch up
                DebugScrambler("Pause 2 seconds to let game server catch up");
                Thread.Sleep(2*1000);

                // Swap players if they have the same clan tag
                if (!KeepSquadsTogether && KeepClanTagsInSameTeam) {
                    if (DebugLevel >= 7) {
                        DebugScrambler("BEFORE SWAPS");
                        ListSideBySide(usScrambled, ruScrambled, true, true);
                    }

                    SwapSameClanTags(ref usScrambled, ref ruScrambled);

                    if (DebugLevel >= 7) {
                        DebugScrambler("AFTER SWAPS");
                        ListSideBySide(usScrambled, ruScrambled, true, true);
                    }
                }

                // Assert that no squad has more than fMaxSquadSize players
                Dictionary<int,int> playerCount = new Dictionary<int,int>();
                foreach (PlayerModel clone in usScrambled) {
                    int num = 0;
                    if (clone.ScrambledSquad < 1 || clone.ScrambledSquad >= SQUAD_NAMES.Length) {
                        ConsoleDebug("ASSERT: After unsquading " + GetTeamName(1) + ", ^b" + clone.FullName + "^n has invalid ScrambledSquad = " + clone.ScrambledSquad);
                        continue;
                    }
                    clone.Squad = 0; // unsquad
                    if (playerCount.TryGetValue(clone.ScrambledSquad, out num)) {
                        num = num + 1;
                    }
                    playerCount[clone.Squad] = num;
                }
                foreach (int squadId in playerCount.Keys) {
                    if (playerCount[squadId] > fMaxSquadSize) {
                        ConsoleDebug("ASSERT: " + GetTeamName(1) + "/" + SQUAD_NAMES[squadId] + " has > " + fMaxSquadSize + " players! = " + playerCount[squadId]);
                    }
                }
                playerCount.Clear();
                foreach (PlayerModel clone in ruScrambled) {
                    int num = 0;
                    if (clone.ScrambledSquad < 1 || clone.ScrambledSquad >= SQUAD_NAMES.Length) {
                        ConsoleDebug("ASSERT: After unsquading " + GetTeamName(2) + ", ^b" + clone.FullName + "^n has invalid ScrambledSquad = " + clone.ScrambledSquad);
                        continue;
                    }
                    clone.Squad = 0; // unsquad
                    if (playerCount.TryGetValue(clone.ScrambledSquad, out num)) {
                        num = num + 1;
                    }
                    playerCount[clone.Squad] = num;
                }
                foreach (int squadId in playerCount.Keys) {
                    if (playerCount[squadId] > fMaxSquadSize) {
                        ConsoleDebug("ASSERT: " + GetTeamName(2) + "/" + SQUAD_NAMES[squadId] + " has > " + fMaxSquadSize + " players! = " + playerCount[squadId]);
                    }
                }
                playerCount.Clear();

                // Now run through each cloned list and move any players that need moving
                DebugScrambler("STARTING SCRAMBLE MOVES");
                ScrambleStatus check = ScrambleTeams(usScrambled, ruScrambled, logOnly);
                DebugScrambler("FINISHED SCRAMBLE MOVES");
                switch (check) {
                    case ScrambleStatus.CompletelyFull:
                        DebugScrambler("SERVER IS COMPLETELY FULL! No scrambling is possible.");
                        break;
                    case ScrambleStatus.Failure:
                        DebugScrambler("UNABLE TO SCRAMBLE, no room to move!");
                        break;
                    case ScrambleStatus.PartialSuccess:
                        DebugScrambler("SCRAMBLE ABORTED! Some moves completed, some failed!");
                        break;
                    case ScrambleStatus.Success:
                    default:
                        break;
                }

                ScheduleListPlayers(1); // refresh

                // For debugging
                foreach (PlayerModel clone in usScrambled) {
                    if (!IsKnownPlayer(clone.Name)) continue;
                    fDebugScramblerAfter[0].Add(clone);
                }
                foreach (PlayerModel clone in ruScrambled) {
                    if (!IsKnownPlayer(clone.Name)) continue;
                    fDebugScramblerAfter[1].Add(clone);
                }

                DebugScrambler("DONE!");
                //if (logOnly || DebugLevel >= 6) CommandToLog("scrambled");
            } catch (Exception e) {
                ConsoleException(e);
            }
            }
            } catch (Exception e) {
            ConsoleException(e);
            } finally {
            fWhileScrambling = false;
            ConsoleWrite("^bScramblerLoop^n thread stopped", 0);
            }
        }
 private void RemapSquad(Dictionary<int,SquadRoster> squadTable, SquadRoster squad)
 {
     int emptyId = 1;
     while (squadTable.ContainsKey(emptyId)) {
     emptyId = emptyId + 1;
     if (emptyId > (SQUAD_NAMES.Length - 1)) {
     if (DebugLevel >= 8) ConsoleDebug("RemapSquad: ran out of empty squads!");
     return;
     }
     }
     squad.Squad = emptyId;
 }
        private void AssignSquadToTeam(SquadRoster squad, Dictionary<int,SquadRoster> squadTable, List<PlayerModel> usScrambled, List<PlayerModel> ruScrambled, List<PlayerModel> target)
        {
            /*
            The PlayerModel object is still live, so we can't change managed properties like Team or Squad.
            Instead, the assigned team is implied by the list (usScrambled or ruScrambled) the player is added to
            and the squad is remembered in the ScrambledSquad property. This is later used during the move
            command to assign the player to that squad in the destination team.
            */
            int teamMax = MaximumServerSize/2;

            if (usScrambled.Count >= teamMax && ruScrambled.Count >= teamMax) {
            DebugScrambler("BOTH teams full! Skipping remaining free pool!");
            return;
            }
            int wasSquad = squad.Roster[0].Squad;

            // Remap if there is a squad collision
            if (squadTable.ContainsKey(squad.Squad)) {
            RemapSquad(squadTable, squad);
            }
            squadTable[squad.Squad] = squad;
            int wasTeam = squad.Roster[0].Team;
            String st = GetTeamName(wasTeam);
            String gt = GetTeamName((target == usScrambled) ? 1 : 2);
            DebugScrambler(st + "/" + SQUAD_NAMES[wasSquad] + " scrambled to " + gt + "/" + SQUAD_NAMES[squad.Squad]);

            // Assign the squad to the target team
            int toTeam = (target == usScrambled) ? 1 : 2;
            foreach (PlayerModel clone in squad.Roster) {
            clone.ScrambledSquad = squad.Squad;
            if (target.Count < teamMax && IsKnownPlayer(clone.Name)) {
                clone.Team = toTeam;
                target.Add(clone);
            }
            }
        }
        private void AssignFillerToTeam(PlayerModel filler, int toTeamId, List<PlayerModel> target, Dictionary<int,SquadRoster> targetSquadTable)
        {
            String who = GetTeamName(toTeamId);
            if ((target.Count + 1) > (MaximumServerSize/2)) {
            DebugScrambler("Team " + who + " is full, skipping filler assignment of ^b" + filler.FullName);
            return;
            }
            if (!IsKnownPlayer(filler.Name)) return; // might have left

            // Find a squad with room to add this player, otherwise create a squad
            int toSquadId = 0;
            int emptyId = 1;
            SquadRoster toSquad = null;
            foreach (int key in targetSquadTable.Keys) {
            toSquad = targetSquadTable[key];
            if (toSquad.Roster.Count == fMaxSquadSize) continue;
            toSquadId = key;
            break;
            }
            if (toSquadId == 0) {
            // Create a new squad
            while (targetSquadTable.ContainsKey(emptyId)) {
            ++emptyId;
            if (emptyId >= SQUAD_NAMES.Length) {
                emptyId = 0;
                break;
            }
            }
            toSquadId = emptyId;
            ConsoleDebug("AssignFillerToTeam: created new squad " + SQUAD_NAMES[toSquadId]);
            } else {
            ConsoleDebug("AssignFillerToTeam: using existing squad " + SQUAD_NAMES[toSquadId]);
            }
            DebugScrambler("Filling in " + who + " team with player ^b" + filler.FullName + "^n to squad " + SQUAD_NAMES[toSquadId]);
            filler.ScrambledSquad = toSquadId;
            filler.Team = toTeamId;
            target.Add(filler);
            toSquad = null;
            if (!targetSquadTable.ContainsKey(toSquadId)) {
            toSquad = new SquadRoster(toSquadId);
            targetSquadTable[toSquadId] = toSquad;
            } else {
            toSquad = targetSquadTable[toSquadId];
            }
            toSquad.Roster.Add(filler);
        }
 private SquadRoster AddPlayerToSquadRoster(Dictionary<int,SquadRoster> squads, PlayerModel player, int key, int squadId, bool ignoreSize)
 {
     SquadRoster squad = null;
     if (squads.TryGetValue(key, out squad)) {
     if (ignoreSize || squad.Roster.Count < fMaxSquadSize) {
     squad.Roster.Add(player);
     }
     } else {
     squad = new SquadRoster(squadId);
     squad.Roster.Add(player);
     squads[key] = squad;
     }
     return squad;
 }