PRoConEvents.MULTIbalancer.ScramblerLoop C# (CSharp) Method

ScramblerLoop() private method

private ScramblerLoop ( ) : void
return void
        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 " + TotalPlayerCount() + " players, winner was T" + fWinner + "(" + 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 team
                            //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 if (CheckWhitelist(clone, WL_BALANCE)) { // Leave Whitelisted players in same team and squad
                            key = (Math.Max(0, clone.Team) * 1000) + Math.Max(0, clone.Squad); // 0 is okay, makes lone-wolf pool
                            DebugScrambler("Keeping whitelisted ^b" + clone.FullName + "^n in same team and squad, using key " + key);

                            SquadRoster tsr = AddPlayerToSquadRoster(squads, clone, key, squadId, true);
                            if (tsr != null) {
                                tsr.WhitelistCount = tsr.WhitelistCount + 1;
                            }
                        } else {
                            loneWolves.Add(clone);
                        }
                    } catch (Exception e) {
                        if (DebugLevel >= 8) ConsoleException(e);
                    }
                }

                // Add lone wolves to empty squads
                ScrambleLoneWolves(loneWolves, squads, 1);
                ScrambleLoneWolves(loneWolves, squads, 2);
                /*
                bool filling = false;
                // Do Team 1 first
                foreach (PlayerModel wolf in loneWolves) {
                    if (wolf.Team != 1)
                        continue;
                    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) ? "T1" : "T2";
                    DebugScrambler(ot + "/" + GetSquadName(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 (IsInDispersalList(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 + "/" + GetSquadName(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) ? "T1" : "T2";
                    DebugScrambler("    " + ScrambleBy + ":" + ds.Metric.ToString("F1") + " " + oldt + "/" + GetSquadName(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 = "T1 (" + GetTeamName(1) + ")";
                        } else if (disp.DispersalGroup == 2 && ruScrambled.Count < teamMax) {
                            localTarget = ruScrambled;
                            debugMsg = "T2 (" + GetTeamName(2) + ")";
                        } else {
                            continue;
                        }
                        DebugScrambler("Squad " + GetSquadName(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 + ": T1(" + usScrambled.Count + ") = " + usMetric.ToString("F1") + ", T2(" + 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 = T1 (" + GetTeamName(1) + ")";
                    } else {
                        target = ruScrambled;
                        targetSquadTable = ruSquads;
                        opposing = usScrambled;
                        debugMsg = "Scrambling to target = T2 (" + 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 = T1 (" + GetTeamName(1) + ")";
                        } else {
                            targetSquadTable = ruSquads;
                            debugMsg = "^4REVISED for count target = T2 (" + GetTeamName(2) + ")";
                        }
                    } else {
                        squad = all[0]; // use strongest squad
                    }

                    if (logOnly || DebugLevel >= 6) {
                        DebugScrambler(" ");
                        DebugScrambler(debugMsg + ", squad " + GetSquadName(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 = "T1 (" + GetTeamName(1) + ") needs " + needed + " more players";
                    } else {
                        target = ruScrambled;
                        targetSquadTable = ruSquads;
                        targetDispersalGroup = 2;
                        toTeamId = 2;
                        opposing = usScrambled;
                        opposingSquadTable = usSquads;
                        oppHaveNoSquad = usHaveNoSquad;
                        oppSquadOfOne = usSquadOfOne;
                        debugMsg = "T2 (" + GetTeamName(2) + ") needs " + needed + " more players";
                    }

                    DebugScrambler("Adjusting team sizes, T1(" + usScrambled.Count + "/" + fTeam1.Count + ") vs T2(" + 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 " + GetTeamName(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 (f == null) break;
                            filler = f;

                            // Check to make sure Dispersal isn't violated
                            if (DivideBy == DivideByChoices.DispersalGroup && IsInDispersalList(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
                            }

                            // Make sure player isn't whitelisted
                            if (CheckWhitelist(filler, WL_BALANCE)) {
                                filler = null;
                                continue;
                            }

                            // 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: T1(" + usScrambled.Count + "), T2(" + 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 T" + clone.Team + "(" + GetTeamName(clone.Team) + "), correcting to T1");
                        clone.Team = 1;
                    }
                }
                foreach (PlayerModel clone in ruScrambled) {
                    if (clone.Team != 2) {
                        ConsoleDebug("WARNING: ^b" + clone.FullName + "^n was in T" + clone.Team + "(" + GetTeamName(clone.Team) + "), correcting to T2");
                        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 T1, ^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: T1/" + GetSquadName(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 T2, ^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: T2/" + GetSquadName(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 (ThreadAbortException) {
            fAborted = true;
            return;
            } catch (Exception e) {
            ConsoleException(e);
            } finally {
            fWhileScrambling = false;
            if (!fAborted) ConsoleWrite("^bScramblerLoop^n thread stopped", 6);
            }
        }
MULTIbalancer