示例#1
0
        /* ======================== CORE ENGINE ============================= */
        private void BalanceAndUnstack(String name)
        {
            /* Useful variables */

            PlayerModel player = null;
            String simpleMode = String.Empty;
            PerModeSettings perMode = null;
            bool isStrong = false; // this player
            int winningTeam = 0;
            int losingTeam = 0;
            int biggestTeam = 0;
            int smallestTeam = 0;
            int[] ascendingSize = null;
            int[] descendingTickets = null;
            String strongMsg = String.Empty;
            int diff = 0;
            DateTime now = DateTime.Now;
            bool needsBalancing = false;
            bool loggedStats = false;
            bool isSQDM = IsSQDM();
            String log = String.Empty;

            /* Sanity checks */

            if (fServerInfo == null) {
            return;
            }

            int totalPlayerCount = this.TotalPlayerCount;

            if (DebugLevel >= 8) DebugBalance("BalanceAndUnstack(^b" + name + "^n), " + totalPlayerCount + " players");

            if (totalPlayerCount >= (MaximumServerSize-1)) {
            if (DebugLevel >= 6) DebugBalance("Server is full, no balancing or unstacking will be attempted!");
            IncrementTotal(); // no matching stat, reflect total deaths handled
            CheckDeativateBalancer("Full");
            return;
            }

            int floorPlayers = 6;
            if (totalPlayerCount < floorPlayers) {
            if (DebugLevel >= 6) DebugBalance("Not enough players in server, minimum is " + floorPlayers);
            CheckDeativateBalancer("Not enough players");
            return;
            }

            if (totalPlayerCount > 0) {
            AnalyzeTeams(out diff, out ascendingSize, out descendingTickets, out biggestTeam, out smallestTeam, out winningTeam, out losingTeam);
            } else {
            CheckDeativateBalancer("Empty");
            return;
            }

            /* Pre-conditions */

            player = GetPlayer(name);
            if (player == null) {
            CheckDeativateBalancer("Unknown player " + name);
            return;
            }

            if (!fModeToSimple.TryGetValue(fServerInfo.GameMode, out simpleMode)) {
            DebugBalance("Unknown game mode: " + fServerInfo.GameMode);
            simpleMode = fServerInfo.GameMode;
            }
            if (String.IsNullOrEmpty(simpleMode)) {
            DebugBalance("Simple mode is null: " + fServerInfo.GameMode);
            CheckDeativateBalancer("Unknown mode");
            return;
            }
            if (!fPerMode.TryGetValue(simpleMode, out perMode)) {
            DebugBalance("No per-mode settings for " + simpleMode + ", using defaults");
            perMode = new PerModeSettings();
            }
            if (perMode == null) {
            DebugBalance("Per-mode settings null for " + simpleMode + ", using defaults");
            perMode = new PerModeSettings();
            }

            /* Per-mode and player info */

            String extractedTag = ExtractTag(player);
            Speed balanceSpeed = GetBalanceSpeed(perMode);
            double unstackTicketRatio = GetUnstackTicketRatio(perMode);
            int lastMoveFrom = player.LastMoveFrom;

            if (totalPlayerCount >= (perMode.MaxPlayers-1)) {
            if (DebugLevel >= 6) DebugBalance("Server is full by per-mode Max Players, no balancing or unstacking will be attempted!");
            IncrementTotal(); // no matching stat, reflect total deaths handled
            CheckDeativateBalancer("Full per-mode");
            return;
            }

            /* Check dispersals */

            bool mustMove = false;
            bool lenient = false;
            int maxDispersalMoves = 2;
            bool isDisperseByRank = IsRankDispersal(player);
            bool isDisperseByList = IsDispersal(player, false);
            if (isDisperseByList) {
            String dispersalMode = (lenient) ? "LENIENT MODE" : "STRICT MODE";
            DebugBalance("ON MUST MOVE LIST ^b" + player.FullName + "^n T:" + player.Team + ", disperse evenly enabled, " + dispersalMode);
            mustMove = true;
            lenient = !perMode.EnableStrictDispersal; // the opposite of strict is lenient
            maxDispersalMoves = (lenient) ? 1 : 2;
            } else if (isDisperseByRank) {
            DebugBalance("ON MUST MOVE LIST ^b" + name + "^n T:" + player.Team + ", Rank " + player.Rank + " >= " + perMode.DisperseEvenlyByRank);
            mustMove = true;
            lenient = LenientRankDispersal;
            maxDispersalMoves = (lenient) ? 1 : 2;
            }

            /* Check if balancing is needed */

            if (diff > MaxDiff()) {
            needsBalancing = true; // needs balancing set to true, unless speed is Unstack only
            if (balanceSpeed == Speed.Unstack) {
            DebugBalance("Needs balancing, but balance speed is set to Unstack, so no balancing will be done");
            needsBalancing = false;
            }
            }

            /* Per-mode settings */

            // Adjust for duration of balance active
            if (needsBalancing && fBalanceIsActive && balanceSpeed == Speed.Adaptive && fLastBalancedTimestamp != DateTime.MinValue) {
            double secs = now.Subtract(fLastBalancedTimestamp).TotalSeconds;
            if (secs > SecondsUntilAdaptiveSpeedBecomesFast) {
            DebugBalance("^8^bBalancing taking too long (" + secs.ToString("F0") + " secs)!^n^0 Forcing to Fast balance speed.");
            balanceSpeed = Speed.Fast;
            }
            }
            String orSlow = (balanceSpeed == Speed.Slow) ? " or speed is Slow" : String.Empty;

            // Do not disperse mustMove players if speed is Stop or Phase is Late
            if (mustMove && balanceSpeed == Speed.Stop) {
            DebugBalance("Removing MUST MOVE status from dispersal player ^b" + player.FullName + "^n T:" + player.Team + ", due to Balance Speed = Stop");
            mustMove = false;
            } else if (mustMove && GetPhase(perMode, false) == Phase.Late) {
            DebugBalance("Removing MUST MOVE status from dispersal player ^b" + player.FullName + "^n T:" + player.Team + ", due to Phase = Late");
            mustMove = false;
            }

            /* Activation check */

            if (balanceSpeed != Speed.Stop && needsBalancing) {
            if (!fBalanceIsActive) {
            DebugBalance("^2^bActivating autobalance!");
            fLastBalancedTimestamp = now;
            }
            fBalanceIsActive = true;
            } else {
            CheckDeativateBalancer("Deactiving autobalance");
            }

            // Wait for unassigned
            if (!mustMove && needsBalancing && balanceSpeed != Speed.Fast && (diff > MaxDiff()) && fUnassigned.Count >= (diff - MaxDiff())) {
            DebugBalance("Wait for " + fUnassigned.Count + " unassigned players to be assigned before moving active players");
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            }

            /* Exclusions */

            // Exclude if on Whitelist or Reserved Slots if enabled
            if (OnWhitelist || (needsBalancing && balanceSpeed == Speed.Slow)) {
            if (CheckWhitelist(player, WL_BALANCE)) {
            DebugBalance("Excluding ^b" + player.FullName + "^n: whitelisted" + orSlow);
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            }
            }

            // Sort player's team by the strong method
            List<PlayerModel> fromList = GetTeam(player.Team);
            if (fromList == null) {
            DebugBalance("Unknown team " + player.Team + " for player ^b" + player.Name);
            return;
            }
            switch (perMode.DetermineStrongPlayersBy) {
            case DefineStrong.RoundScore:
            fromList.Sort(DescendingRoundScore);
            strongMsg = "Determing strong by: Round Score";
            break;
            case DefineStrong.RoundSPM:
            fromList.Sort(DescendingRoundSPM);
            strongMsg = "Determing strong by: Round SPM";
            break;
            case DefineStrong.RoundKills:
            fromList.Sort(DescendingRoundKills);
            strongMsg = "Determing strong by: Round Kills";
            break;
            case DefineStrong.RoundKDR:
            fromList.Sort(DescendingRoundKDR);
            strongMsg = "Determing strong by: Round KDR";
            break;
            case DefineStrong.PlayerRank:
            fromList.Sort(DescendingPlayerRank);
            strongMsg = "Determing strong by: Player Rank";
            break;
            case DefineStrong.RoundKPM:
            fromList.Sort(DescendingRoundKPM);
            strongMsg = "Determing strong by: Round KPM";
            break;
            case DefineStrong.BattlelogSPM:
            fromList.Sort(DescendingSPM);
            strongMsg = "Determing strong by: Battlelog SPM";
            break;
            case DefineStrong.BattlelogKDR:
            fromList.Sort(DescendingKDR);
            strongMsg = "Determing strong by: Battlelog KDR";
            break;
            case DefineStrong.BattlelogKPM:
            fromList.Sort(DescendingKPM);
            strongMsg = "Determing strong by: Battlelog KPM";
            break;
            default:
            fromList.Sort(DescendingRoundScore);
            strongMsg = "Determing strong by: Round Score";
            break;
            }

            double above = ((fromList.Count * perMode.PercentOfTopOfTeamIsStrong) / 100.0) + 0.5;
            int strongest = Math.Max(0, Convert.ToInt32(above));
            int playerIndex = 0;
            int minPlayers = (isSQDM) ? 5 : fromList.Count; // for SQDM, apply top/strong/weak only if team has 5 or more players

            // Exclude if TopScorers enabled and a top scorer on the team
            int topPlayersPerTeam = 0;
            if (balanceSpeed != Speed.Fast && (TopScorers || balanceSpeed == Speed.Slow)) {
            if (isSQDM) {
            int maxCount = fromList.Count;
            if (maxCount < 5) {
                topPlayersPerTeam = 0;
            } else if (maxCount <= 8) {
                topPlayersPerTeam = 1;
            } else if (totalPlayerCount <= 16) {
                topPlayersPerTeam = 2;
            } else {
                topPlayersPerTeam = 3;
            }
            } else {
            if (totalPlayerCount <= 22) {
                topPlayersPerTeam = 1;
            } else if (totalPlayerCount >= 42) {
                topPlayersPerTeam = 3;
            } else {
                topPlayersPerTeam = 2;
            }
            }
            }
            for (int i = 0; i < fromList.Count; ++i) {
            if (fromList[i].Name == player.Name) {
            if (!mustMove
            && needsBalancing
            && balanceSpeed != Speed.Fast
            && fromList.Count >= minPlayers
            && topPlayersPerTeam != 0
            && i < topPlayersPerTeam) {
                String why = (balanceSpeed == Speed.Slow) ? "Speed is slow, excluding top scorers" : "Top Scorers enabled";
                if (!loggedStats) {
                    DebugBalance(GetPlayerStatsString(name));
                    loggedStats = true;
                }
                DebugBalance("Excluding ^b" + player.FullName + "^n: " + why + " and this player is #" + (i+1) + " on team " + GetTeamName(player.Team));
                fExcludedRound = fExcludedRound + 1;
                IncrementTotal();
                return;
            } else {
                playerIndex = i;
                break;
            }
            }
            }
            isStrong = (playerIndex < strongest);

            // Exclude if too soon since last move
            if ((!mustMove || lenient) && player.MovedByMBTimestamp != DateTime.MinValue) {
            double mins = DateTime.Now.Subtract(player.MovedByMBTimestamp).TotalMinutes;
            if (mins < MinutesAfterBeingMoved) {
            DebugBalance("Excluding ^b" + player.Name + "^n: last move was " + mins.ToString("F0") + " minutes ago, less than required " + MinutesAfterBeingMoved.ToString("F0") + " minutes");
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            } else {
            // reset
            player.MovedByMBTimestamp = DateTime.MinValue;
            }
            }

            // Exclude if player joined less than MinutesAfterJoining
            double joinedMinutesAgo = GetPlayerJoinedTimeSpan(player).TotalMinutes;
            double enabledForMinutes = now.Subtract(fEnabledTimestamp).TotalMinutes;
            if ((!mustMove || lenient)
            && needsBalancing
            && (enabledForMinutes > MinutesAfterJoining)
            && balanceSpeed != Speed.Fast
            && (joinedMinutesAgo < MinutesAfterJoining)) {
            if (!loggedStats) {
            DebugBalance(GetPlayerStatsString(name));
            loggedStats = true;
            }
            DebugBalance("Excluding ^b" + player.FullName + "^n: joined less than " + MinutesAfterJoining.ToString("F1") + " minutes ago (" + joinedMinutesAgo.ToString("F1") + ")");
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            }

            // Special exemption if tag not verified and first/partial round
            if (!player.TagVerified && player.Rounds <= 1) {
            if (DebugLevel >= 7) DebugBalance("Skipping ^b" + player.Name + "^n, clan tag not verified yet");
            // Don't count this as an excemption
            // Don't increment the total
            return;
            }

            // Exclude if in squad with same tags
            if ((!mustMove || lenient) && SameClanTagsInSquad) {
            int cmt =  CountMatchingTags(player, Scope.SameSquad);
            if (cmt >= 2) {
            String et = ExtractTag(player);
            DebugBalance("Excluding ^b" + name + "^n, " + cmt + " players in squad with tag [" + et + "]");
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            }
            }

            // Exclude if in team with same tags
            if ((!mustMove || lenient) && SameClanTagsInTeam) {
            int cmt =  CountMatchingTags(player, Scope.SameTeam);
            if (cmt >= 5) {
            String et = ExtractTag(player);
            DebugBalance("Excluding ^b" + name + "^n, " + cmt + " players in team with tag [" + et + "]");
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            }
            }

            // Exclude if on friends list
            if ((!mustMove || lenient) && OnFriendsList) {
            int cmf = CountMatchingFriends(player, Scope.SameSquad);
            if (cmf >= 2) {
            DebugBalance("Excluding ^b" + player.FullName + "^n, " + cmf + " players in squad are friends (friendex = " + player.Friendex + ")");
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            }
            if (ApplyFriendsListToTeam) {
            cmf = CountMatchingFriends(player, Scope.SameTeam);
            if (cmf >= 5) {
                DebugBalance("Excluding ^b" + player.FullName + "^n, " + cmf + " players in team are friends (friendex = " + player.Friendex + ")");
                fExcludedRound = fExcludedRound + 1;
                IncrementTotal();
                return;
            }
            }
            }

            // Exempt if this player already been moved for balance or unstacking
            if ((!mustMove && GetMoves(player) >= 1) || (mustMove && GetMoves(player) >= maxDispersalMoves)) {
            DebugBalance("Exempting ^b" + name + "^n, already moved this round");
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            /* Balance */

            int toTeamDiff = 0;
            int toTeam = ToTeam(name, player.Team, false, out toTeamDiff, ref mustMove); // take into account dispersal by Rank, etc.

            if (toTeam == 0 || toTeam == player.Team) {
            if (needsBalancing || mustMove) {
            if (DebugLevel >= 8) DebugBalance("Exempting ^b" + name + "^n, target team selected is same or zero");
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }
            }

            int numTeams = 2; //(isSQDM) ? 4 : 2; // TBD, what is max squad size for SQDM?
            int maxTeamSlots = (MaximumServerSize/numTeams);
            int maxTeamPerMode = (perMode.MaxPlayers/numTeams);
            List<PlayerModel> lt = GetTeam(toTeam);
            int toTeamSize = (lt == null) ? 0 : lt.Count;

            if (toTeamSize == maxTeamSlots || toTeamSize == maxTeamPerMode) {
            if (DebugLevel >= 8) DebugBalance("Exempting ^b" + name + "^n, target team is full " + toTeamSize);
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            if (mustMove) DebugBalance("^4MUST MOVE^0 ^b" + name + "^n from " + GetTeamName(player.Team) + " to " + GetTeamName(toTeam));

            if ((!mustMove || lenient) && needsBalancing && toTeamDiff <= MaxDiff()) {
            DebugBalance("Exempting ^b" + name + "^n, difference between " + GetTeamName(player.Team) + " team and " + GetTeamName(toTeam) + " team is only " + toTeamDiff);
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            if ((fBalanceIsActive || mustMove) && toTeam != 0) {
            String ts = null;
            if (isSQDM) {
            ts = fTeam1.Count + "(A) vs " + fTeam2.Count + "(B) vs " + fTeam3.Count + "(C) vs " + fTeam4.Count + "(D)";
            } else {
            ts = fTeam1.Count + "(US) vs " + fTeam2.Count + "(RU)";
            }
            if (mustMove) {
            DebugBalance("Autobalancing because ^b" + name + "^n must be moved");
            } else {
            DebugBalance("Autobalancing because difference of " + diff + " is greater than " + MaxDiff() + ", [" + ts + "]");
            }
            double abTime = now.Subtract(fLastBalancedTimestamp).TotalSeconds;
            if (abTime > 0) {
            DebugBalance("^2^bAutobalance has been active for " + abTime.ToString("F1") + " seconds!");
            }

            if (!loggedStats) {
            DebugBalance(GetPlayerStatsString(name) + ((isStrong) ? " STRONG" : " WEAK"));
            loggedStats = true;
            }

            /* Exemptions */

            // Already on the smallest team
            if ((!mustMove || lenient) && player.Team == smallestTeam) {
            DebugBalance("Exempting ^b" + name + "^n, already on the smallest team");
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            // SQDM, not on the biggest team
            if (isSQDM && !mustMove && balanceSpeed != Speed.Fast && player.Team != biggestTeam) {
            // Make sure player's team isn't the same size as biggest
            List<PlayerModel> aTeam = GetTeam(player.Team);
            List<PlayerModel> bigTeam = GetTeam(biggestTeam);
            if (aTeam == null || bigTeam == null || (aTeam != null && bigTeam != null && aTeam.Count < bigTeam.Count)) {
                DebugBalance("Exempting ^b" + name + "^n, not on the biggest team");
                fExemptRound = fExemptRound + 1;
                IncrementTotal();
                return;
            }
            }

            // Exempt if only moving weak players and is strong
            if (!mustMove && perMode.OnlyMoveWeakPlayers && isStrong) {
            DebugBalance("Exempting strong ^b" + name + "^n, Only Move Weak Players set to True for " + simpleMode);
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            // Strong/Weak exemptions and clan tag
            if (!mustMove && balanceSpeed != Speed.Fast && fromList.Count >= minPlayers) {
            if (DebugLevel > 5) DebugBalance(strongMsg);
            // don't move weak player to losing team, unless we are only moving weak players
            if (!isStrong  && toTeam == losingTeam && !perMode.OnlyMoveWeakPlayers) {
                DebugBalance("Exempting ^b" + name + "^n, don't move weak player to losing team (#" + (playerIndex+1) + " of " + fromList.Count + ", top " + (strongest) + ")");
                fExemptRound = fExemptRound + 1;
                IncrementTotal();
                return;
            }

            // don't move strong player to winning team
            if (isStrong && toTeam == winningTeam) {
                DebugBalance("Exempting ^b" + name + "^n, don't move strong player to winning team (#" + (playerIndex+1) + " of " + fromList.Count + ", median " + (strongest) + ")");
                fExemptRound = fExemptRound + 1;
                IncrementTotal();
                return;
            }

            // Don't move to same team
            if (player.Team == toTeam) {
                if (DebugLevel >= 7) DebugBalance("Exempting ^b" + name + "^n, don't move player to his own team!");
                IncrementTotal(); // no matching stat, reflect total deaths handled
                return;
            }
            }

            /* Move for balance */

            int origTeam = player.Team;
            String origName = GetTeamName(player.Team);

            if (lastMoveFrom != 0) {
            origTeam = lastMoveFrom;
            origName = GetTeamName(origTeam);
            }

            MoveInfo move = new MoveInfo(name, player.Tag, origTeam, origName, toTeam, GetTeamName(toTeam));
            move.For = MoveType.Balance;
            move.Format(this, ChatMovedForBalance, false, false);
            move.Format(this, YellMovedForBalance, true, false);
            String why = (mustMove) ? "to disperse evenly" : ("because difference is " + diff);
            log = "^4^bBALANCE^n^0 moving ^b" + player.FullName + "^n from " + move.SourceName + " team to " + move.DestinationName + " team " + why;
            log = (EnableLoggingOnlyMode) ? "^9(SIMULATING)^0 " + log : log;
            DebugWrite(log, 3);

            DebugWrite("^9" + move, 8);

            player.LastMoveFrom = player.Team;
            StartMoveImmediate(move, false);

            if (EnableLoggingOnlyMode) {
            // Simulate completion of move
            OnPlayerTeamChange(name, toTeam, 0);
            OnPlayerMovedByAdmin(name, toTeam, 0, false); // simulate reverse order
            }
            // no increment total, handled later when move is processed
            return;
            }

            if (!fBalanceIsActive) {
            fLastBalancedTimestamp = now;
            if (DebugLevel >= 8) ConsoleDebug("fLastBalancedTimestamp = " + fLastBalancedTimestamp.ToString("HH:mm:ss"));
            }

            /* Unstack */

            // Not enabled or not full round
            if (!EnableUnstacking) {
            if (DebugLevel >= 8) DebugBalance("Unstack is disabled, Enable Unstacking is set to False");
            IncrementTotal();
            return;
            } else if (!fIsFullRound) {
            if (DebugLevel >= 7) DebugBalance("Unstack is disabled, not a full round");
            IncrementTotal();
            return;
            }

            // Sanity checks
            if (winningTeam <= 0 || winningTeam >= fTickets.Length || losingTeam <= 0 || losingTeam >= fTickets.Length || balanceSpeed == Speed.Stop) {
            if (DebugLevel >= 5) DebugBalance("Skipping unstack for player that was killed ^b" + name +"^n: winning = " + winningTeam + ", losingTeam = " + losingTeam + ", speed = " + balanceSpeed);
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            }

            // Server is full, can't swap
            if (totalPlayerCount > (MaximumServerSize-2) || totalPlayerCount > (perMode.MaxPlayers-2)) {
            // TBD - kick idle players?
            if (DebugLevel >= 7) DebugBalance("No room to swap players for unstacking");
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            }

            // Disabled per-mode
            if (perMode.CheckTeamStackingAfterFirstMinutes == 0) {
            if (DebugLevel >= 5) DebugBalance("Unstacking has been disabled, Check Team Stacking After First Minutes set to zero");
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            }

            double tirMins = GetTimeInRoundMinutes();

            // Too soon to unstack
            if (tirMins < perMode.CheckTeamStackingAfterFirstMinutes) {
            DebugBalance("Too early to check for unstacking, skipping ^b" + name + "^n");
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            // Maximum swaps already done
            if ((fUnstackedRound/2) >= perMode.MaxUnstackingSwapsPerRound) {
            if (DebugLevel >= 6) DebugBalance("Maximum swaps have already occurred this round (" + (fUnstackedRound/2) + ")");
            fUnstackState = UnstackState.Off;
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            }

            // Whitelisted
            if (OnWhitelist) {
            if (CheckWhitelist(player, WL_UNSTACK)) {
            DebugBalance("Excluding from unstacking due to being whitelisted, ^b" + name + "^n");
            fExcludedRound = fExcludedRound + 1;
            IncrementTotal();
            return;
            }
            }

            double ratio = 1;
            double t1Tickets = 0;
            double t2Tickets = 0;
            if (IsCTF()) {
            // Use team points, not tickets
            double usPoints = GetTeamPoints(1);
            double ruPoints = GetTeamPoints(2);
            if (usPoints <= 0) usPoints = 1;
            if (ruPoints <= 0) ruPoints = 1;
            ratio = (usPoints > ruPoints) ? (usPoints/ruPoints) : (ruPoints/usPoints);
            } else {
            // Otherwise use ticket ratio
            if (fTickets[losingTeam] >= 1) {
            if (IsRush()) {
                // normalize Rush ticket ratio
                double attackers = fTickets[1];
                double defenders = fMaxTickets - (fRushMaxTickets - fTickets[2]);
                defenders = Math.Max(defenders, attackers/2);
                ratio = (attackers > defenders) ? (attackers/Math.Max(1, defenders)) : (defenders/Math.Max(1, attackers));
                t1Tickets = attackers;
                t2Tickets = defenders;
            } else {
                t1Tickets = Convert.ToDouble(fTickets[winningTeam]);
                t2Tickets = Convert.ToDouble(fTickets[losingTeam]);
                ratio =  t1Tickets / Math.Max(1, t2Tickets);
            }
            }
            }

            // Ticket difference greater than per-mode maximum for unstacking
            int ticketGap = Convert.ToInt32(Math.Abs(t1Tickets - t2Tickets));
            if (perMode.MaxUnstackingTicketDifference > 0 && ticketGap > perMode.MaxUnstackingTicketDifference) {
            DebugBalance("Ticket difference of " + ticketGap + " exceeds Max Unstacking Ticket Difference of " + perMode.MaxUnstackingTicketDifference + ", skipping ^b" + name + "^n");
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            String um = "Ticket ratio " + (ratio*100.0).ToString("F0") + " vs. unstack ratio of " + (unstackTicketRatio*100.0).ToString("F0");

            // Using ticket loss instead of ticket ratio?
            if (perMode.EnableTicketLossRatio && false) { // disable for this release
            double a1 = GetAverageTicketLossRate(1, false);
            double a2 = GetAverageTicketLossRate(2, false);
            ratio = (a1 > a2) ? (a1/Math.Max(1, a2)) : (a2/Math.Max(1, a1));
            ratio = Math.Min(ratio, 50.0); // cap at 50x
            um = "Ticket loss ratio is " + (ratio*100.0).ToString("F0") + " vs. unstack ratio of " + (unstackTicketRatio*100.0).ToString("F0");

            // Don't unstack if the team with the highest loss rate is the winning team
            // We don't want to send strong players to the team with the highest score!
            if ((a1 > a2 && winningTeam == 1)
            ||  (a2 > a1 && winningTeam == 2)) {
            if (DebugLevel >= 7) DebugBalance("Team with highest ticket loss rate is the winning team, do not unstack: " + a1.ToString("F1") + " vs " + a2.ToString("F1") + ", winning team is " + TeamName(winningTeam));
            IncrementTotal();
            return;
            }
            }

            if (unstackTicketRatio == 0 || ratio < unstackTicketRatio) {
            if (DebugLevel >= 6) DebugBalance("No unstacking needed: " + um);
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            }

            /*
            Cases:
            1) Never unstacked before, timer is 0 and group count is 0
            2) Within a group, timer is 0 and group count is > 0 but < max
            3) Between groups, timer is > 0 and group count is 0
            */

            double nsis = NextSwapGroupInSeconds(perMode); // returns 0 for case 1 and case 2

            if (nsis > 0) {
            if (DebugLevel >= 6) DebugBalance("Too soon to do another unstack swap group, wait another " + nsis.ToString("F1") + " seconds!");
            IncrementTotal(); // no matching stat, reflect total deaths handled
            return;
            } else {
            fFullUnstackSwapTimestamp = DateTime.MinValue; // turn off timer
            }

            // Are the minimum number of players present to decide strong vs weak?
            if (!mustMove && balanceSpeed != Speed.Fast && fromList.Count < minPlayers) {
            DebugBalance("Not enough players in team to determine strong vs weak, skipping ^b" + name + "^n, ");
            fExemptRound = fExemptRound + 1;
            IncrementTotal();
            return;
            }

            // Otherwise, unstack!
            DebugBalance("^6Unstacking!^0 " + um);

            if (DebugLevel >= 6) {
            if (isStrong) {
            DebugBalance("Player ^b" + player.Name + "^n is strong: #" + (playerIndex+1) + " of " + fromList.Count + ", above #" + strongest + " at " + perMode.PercentOfTopOfTeamIsStrong.ToString("F0") + "%");
            } else {
            DebugBalance("Player ^b" + player.Name + "^n is weak: #" + (playerIndex+1) + " of " + fromList.Count + ", equal or below #" + strongest + " at " + perMode.PercentOfTopOfTeamIsStrong.ToString("F0") + "%");
            }
            }

            if (!loggedStats) {
            DebugBalance(GetPlayerStatsString(name));
            loggedStats = true;
            }

            MoveInfo moveUnstack = null;

            int origUnTeam = player.Team;
            String origUnName = GetTeamName(player.Team);
            String strength = "strong";

            if (lastMoveFrom != 0) {
            origUnTeam = lastMoveFrom;
            origUnName = GetTeamName(origUnTeam);
            }

            if (fUnstackState == UnstackState.Off) {
            // First swap
            DebugBalance("For ^b" + name + "^n, first swap of " + perMode.NumberOfSwapsPerGroup);
            fUnstackState = UnstackState.SwappedWeak;
            }

            switch (fUnstackState) {
            case UnstackState.SwappedWeak:
            // Swap strong to losing team
            if (isStrong) {
                // Don't move to same team
                if (player.Team == losingTeam) {
                    if (DebugLevel >= 6) DebugBalance("Skipping strong ^b" + name + "^n, don't move player to his own team!");
                    fExemptRound = fExemptRound + 1;
                    IncrementTotal();
                    return;
                }
                DebugBalance("Sending strong player ^0^b" + player.FullName + "^n^9 to losing team " + GetTeamName(losingTeam));
                moveUnstack = new MoveInfo(name, player.Tag, origUnTeam, origUnName, losingTeam, GetTeamName(losingTeam));
                toTeam = losingTeam;
                fUnstackState = UnstackState.SwappedStrong;
                if (EnableTicketLossRateLogging) UpdateTicketLossRateLog(DateTime.Now, losingTeam, 0);
            } else {
                DebugBalance("Skipping ^b" + name + "^n, don't move weak player to losing team (#" + (playerIndex+1) + " of " + fromList.Count + ", median " + (strongest) + ")");
                fExemptRound = fExemptRound + 1;
                IncrementTotal();
                return;
            }
            break;
            case UnstackState.SwappedStrong:
            // Swap weak to winning team
            if (!isStrong) {
                // Don't move to same team
                if (player.Team == winningTeam) {
                    if (DebugLevel >= 6) DebugBalance("Skipping weak ^b" + name + "^n, don't move player to his own team!");
                    fExemptRound = fExemptRound + 1;
                    IncrementTotal();
                    return;
                }
                DebugBalance("Sending weak player ^0^b" + player.FullName + "^n^9 to winning team " + GetTeamName(winningTeam));
                moveUnstack = new MoveInfo(name, player.Tag, origUnTeam, origUnName, winningTeam, GetTeamName(winningTeam));
                toTeam = winningTeam;
                fUnstackState = UnstackState.SwappedWeak;
                strength = "weak";
                FinishedFullSwap(name, perMode); // updates group count
                if (EnableTicketLossRateLogging) UpdateTicketLossRateLog(DateTime.Now, 0, winningTeam);
            } else {
                DebugBalance("Skipping ^b" + name + "^n, don't move strong player to winning team (#" + (playerIndex+1) + " of " + fromList.Count + ", median " + (strongest) + ")");
                fExemptRound = fExemptRound + 1;
                IncrementTotal();
                return;
            }
            break;
            case UnstackState.Off:
            // fall thru
            default: return;
            }

            /* Move for unstacking */

            log = "^4^bUNSTACK^n^0 moving " + strength + " ^b" + player.FullName + "^n from " + moveUnstack.SourceName + " to " + moveUnstack.DestinationName + " because: " + um;
            log = (EnableLoggingOnlyMode) ? "^9(SIMULATING)^0 " + log : log;
            DebugWrite(log, 3);
            moveUnstack.For = MoveType.Unstack;
            moveUnstack.Format(this, ChatMovedToUnstack, false, false);
            moveUnstack.Format(this, YellMovedToUnstack, true, false);

            DebugWrite("^9" + moveUnstack, 8);

            if (player.LastMoveFrom == 0) player.LastMoveFrom = player.Team;
            StartMoveImmediate(moveUnstack, false);

            if (EnableLoggingOnlyMode) {
            // Simulate completion of move
            OnPlayerTeamChange(name, toTeam, 0);
            OnPlayerMovedByAdmin(name, toTeam, 0, false); // simulate reverse order
            }
            // no increment total, handled by unstacking move
        }
示例#2
0
        private void ResetRound()
        {
            ClearTeams();

            for (int i = 0; i < fTickets.Length; i++) {
            fTickets[i] = 0;
            }

            fRoundStartTimestamp = DateTime.Now;
            fFullUnstackSwapTimestamp = DateTime.MinValue;

            lock (fAllPlayers) {
            foreach (String name in fAllPlayers) {
            try {
                if (!fKnownPlayers.ContainsKey(name)) {
                    ConsoleDebug("ResetRound: " + name + " not in fKnownPlayers");
                    continue;
                }
                PlayerModel m = null;
                lock (fKnownPlayers) {
                    m = fKnownPlayers[name];
                }

                m.ResetRound();
            } catch (Exception e) {
                ConsoleException(e);
            }
            }
            }

            fBalancedRound = 0;
            fUnstackedRound = 0;
            fUnswitchedRound = 0;
            fExcludedRound = 0;
            fExemptRound = 0;
            fFailedRound = 0;
            fTotalRound = 0;
            fReassignedRound = 0;
            fUnstackState = UnstackState.Off;
            fRushStage = 0;
            fRushPrevAttackerTickets = 0;
            fTimeOutOfJoint = 0;
            fRoundsEnabled = fRoundsEnabled + 1;
            fGrandTotalQuits = fGrandTotalQuits + fTotalQuits;
            fTotalQuits = 0;
            fGrandRageQuits = fGrandRageQuits + fRageQuits;
            fRageQuits = 0;

            fLastBalancedTimestamp = DateTime.MinValue;

            ResetAverageTicketLoss();
            fTicketLossHistogram.Clear();
        }
示例#3
0
        /* Constructor */
        public MULTIbalancer()
        {
            /* Private members */
            fIsEnabled = false;
            fFinalizerActive = false;
            fPluginState = PluginState.Disabled;
            fGameState = GameState.Unknown;
            fServerInfo = null;
            fRefreshCommand = false;
            fServerUptime = 0;
            fServerCrashed = false;
            fDebugScramblerBefore = new List<PlayerModel>[2]{new List<PlayerModel>(), new List<PlayerModel>()};
            fDebugScramblerAfter = new List<PlayerModel>[2]{new List<PlayerModel>(), new List<PlayerModel>()};
            fDebugScramblerStartRound = new List<PlayerModel>[2]{new List<PlayerModel>(), new List<PlayerModel>()};

            fBalancedRound = 0;
            fUnstackedRound = 0;
            fUnswitchedRound = 0;
            fExcludedRound = 0;
            fExemptRound = 0;
            fFailedRound = 0;
            fTotalRound = 0;
            fBalanceIsActive = false;
            fRoundsEnabled = 0;
            fGrandTotalQuits = 0;
            fGrandRageQuits = 0;
            fTotalQuits = 0;
            fRageQuits = 0;

            fMoveThread = null;
            fFetchThread = null;
            fListPlayersThread = null;
            fScramblerThread = null;
            fTimerThread = null;

            fModeToSimple = new Dictionary<String,String>();

            fEasyTypeDict = new Dictionary<int, Type>();
            fEasyTypeDict.Add(0, typeof(int));
            fEasyTypeDict.Add(1, typeof(Int16));
            fEasyTypeDict.Add(2, typeof(Int32));
            fEasyTypeDict.Add(3, typeof(Int64));
            fEasyTypeDict.Add(4, typeof(float));
            fEasyTypeDict.Add(5, typeof(long));
            fEasyTypeDict.Add(6, typeof(String));
            fEasyTypeDict.Add(7, typeof(string));
            fEasyTypeDict.Add(8, typeof(double));

            fBoolDict = new Dictionary<int, Type>();
            fBoolDict.Add(0, typeof(Boolean));
            fBoolDict.Add(1, typeof(bool));

            fListStrDict = new Dictionary<int, Type>();
            fListStrDict.Add(0, typeof(String[]));

            fPerMode = new Dictionary<String,PerModeSettings>();

            fAllPlayers = new List<String>();
            fKnownPlayers = new Dictionary<String, PlayerModel>();
            fTeam1 = new List<PlayerModel>();
            fTeam2 = new List<PlayerModel>();
            fTeam3 = new List<PlayerModel>();
            fTeam4 = new List<PlayerModel>();
            fUnassigned = new List<String>();
            fRoundStartTimestamp = DateTime.MinValue;
            fRoundOverTimestamp = DateTime.MinValue;
            fListPlayersTimestamp = DateTime.MinValue;
            fFullUnstackSwapTimestamp = DateTime.MinValue;
            fLastValidationTimestamp = DateTime.MinValue;
            fListPlayersQ = new Queue<DelayedRequest>();

            fPendingTeamChange = new Dictionary<String,int>();
            fMoving = new Dictionary<String, MoveInfo>();
            fMoveQ = new Queue<MoveInfo>();
            fReassigned = new List<String>();
            fReservedSlots = new List<String>();
            fTickets = new int[5]{0,0,0,0,0};
            fFriendlyMaps = new Dictionary<String,String>();
            fFriendlyModes = new Dictionary<String,String>();
            fMaxTickets = -1;
            fRushMaxTickets = -1;
            fLastBalancedTimestamp = DateTime.MinValue;
            fEnabledTimestamp = DateTime.MinValue;
            fFinalStatus = null;
            fIsFullRound = false;
            fUnstackState = UnstackState.Off;
            fLastMsg = null;
            fRushStage = 0;
            fRushPrevAttackerTickets = 0;
            fRushAttackerStageLoss = 0;
            fRushAttackerStageSamples = 0;
            fMoveStash = new List<MoveInfo>();
            fLastVersionCheckTimestamp = DateTime.MinValue;
            fTimeOutOfJoint = 0;
            fUnstackGroupCount = 0;
            fPriorityFetchQ = new PriorityQueue(this);
            fIsCacheEnabled = false;
            fScramblerLock = new DelayedRequest();
            fWinner = 0;
            fUpdateThreadLock = new DelayedRequest();
            fLastServerInfoTimestamp = DateTime.Now;
            fStageInProgress = false;
            fHost = String.Empty;
            fPort = String.Empty;
            fRushMap3Stages = new List<String>(new String[6]{"MP_007", "XP4_Quake", "XP5_002", "MP_012", "XP4_Rubble", "MP_Damage"});
            fRushMap5Stages = new List<String>(new String[6]{"MP_013", "XP3_Valley", "MP_017", "XP5_001", "MP_Prison", "MP_Siege"});
            fGroupAssignments = new int[5]{0,0,0,0,0};
            fDispersalGroups = new List<String>[5]{null, new List<String>(), new List<String>(), new List<String>(), new List<String>()};
            fNeedPlayerListUpdate = false;
            fFriends = new Dictionary<int, List<String>>();
            fAllFriends = new List<String>();
            fWhileScrambling = false;
            fExtrasLock = new DelayedRequest();
            fExtraNames = new List<String>();
            fGotLogin = false;
            fDebugScramblerSuspects = new Dictionary<String,String>();
            fTimerRequestList = new List<DelayedRequest>();
            fAverageTicketLoss = new Queue<double>[3]{null, new Queue<double>(), new Queue<double>()};
            fTicketLossHistogram = new Histogram();

            /* Settings */

            /* ===== SECTION 0 - Presets ===== */

            SettingsVersion = 1;
            Preset = PresetItems.Standard;
            EnableUnstacking = false;
            EnableSettingsWizard = false;
            WhichMode = "Conquest Large";
            MetroIsInMapRotation = false;
            MaximumPlayersForMode = 64;
            LowestMaximumTicketsForMode = 300;
            HighestMaximumTicketsForMode = 400;
            PreferredStyleOfBalancing = PresetItems.Standard;
            ApplySettingsChanges = false;

            /* ===== SECTION 1 - Settings ===== */

            DebugLevel = 2;
            MaximumServerSize = 64;
            EnableBattlelogRequests = true;
            MaximumRequestRate = 10; // in 20 seconds
            WaitTimeout = 30; // seconds
            WhichBattlelogStats = BattlelogStats.ClanTagOnly;
            MaxTeamSwitchesByStrongPlayers = 1;
            MaxTeamSwitchesByWeakPlayers = 2;
            UnlimitedTeamSwitchingDuringFirstMinutesOfRound = 5.0;
            Enable2SlotReserve = false;
            EnablerecruitCommand = false;
            EnableWhitelistingOfReservedSlotsList = true;
            Whitelist = new String[] {DEFAULT_LIST_ITEM};
            fSettingWhitelist = new List<String>(Whitelist);
            DisperseEvenlyList = new String[] {DEFAULT_LIST_ITEM};
            fSettingDisperseEvenlyList = new List<String>(DisperseEvenlyList);
            FriendsList = new String[] {DEFAULT_LIST_ITEM};
            fSettingFriendsList = new List<String>();
            SecondsUntilAdaptiveSpeedBecomesFast = 3*60; // 3 minutes default
            EnableInGameCommands = true;

            /* ===== SECTION 2 - Exclusions ===== */

            OnWhitelist = true;
            OnFriendsList = false;
            ApplyFriendsListToTeam = false;
            TopScorers = true;
            SameClanTagsInSquad = true;
            SameClanTagsInTeam = false;
            SameClanTagsForRankDispersal = false;
            LenientRankDispersal = false;
            MinutesAfterJoining = 5;
            MinutesAfterBeingMoved = 90; // 1.5 hours
            JoinedEarlyPhase = true;
            JoinedMidPhase = true;
            JoinedLatePhase = false;

            /* ===== SECTION 3 - Round Phase & Population Settings ===== */

            EarlyPhaseTicketPercentageToUnstack = new double[3]         {  0,120,120};
            MidPhaseTicketPercentageToUnstack = new double[3]           {  0,120,120};
            LatePhaseTicketPercentageToUnstack = new double[3]          {  0,  0,  0};

            EnableTicketLossRateLogging = false;

            SpellingOfSpeedNamesReminder = Speed.Click_Here_For_Speed_Names;

            EarlyPhaseBalanceSpeed = new Speed[3]           {     Speed.Fast, Speed.Adaptive, Speed.Adaptive};
            MidPhaseBalanceSpeed = new Speed[3]             {     Speed.Fast, Speed.Adaptive, Speed.Adaptive};
            LatePhaseBalanceSpeed = new Speed[3]            {     Speed.Stop,     Speed.Stop,     Speed.Stop};

            /* ===== SECTION 4 - Scrambler ===== */

            OnlyOnNewMaps = true; // false means scramble every round
            OnlyOnFinalTicketPercentage = 120; // 0 means scramble regardless of final score
            ScrambleBy = DefineStrong.RoundScore;
            KeepSquadsTogether = true;
            KeepClanTagsInSameTeam = true;
            KeepFriendsInSameTeam = false;
            DivideBy = DivideByChoices.None;
            ClanTagToDivideBy = String.Empty;
            DelaySeconds = 50;

            /* ===== SECTION 5 - Messages ===== */

            QuietMode = false; // false: chat is global, true: chat is private. Yells are always private
            YellDurationSeconds = 10;
            BadBecauseMovedByBalancer = "autobalance moved you to the %toTeam% team";
            BadBecauseWinningTeam = "switching to the winning team is not allowed";
            BadBecauseBiggestTeam = "switching to the biggest team is not allowed";
            BadBecauseRank = "this server splits Colonel 100's between teams";
            BadBecauseDispersalList = "you're on the list of players to split between teams";
            ChatMovedForBalance = "*** MOVED %name% for balance ...";
            YellMovedForBalance = "Moved %name% for balance ...";
            ChatMovedToUnstack = "*** MOVED %name% to unstack teams ...";
            YellMovedToUnstack = "Moved %name% to unstack teams ...";
            ChatDetectedBadTeamSwitch = "%name%, you can't switch to team %fromTeam%: %reason%, sending you back ...";
            YellDetectedBadTeamSwitch = "You can't switch to the %fromTeam% team: %reason%, sending you back!";
            ChatDetectedGoodTeamSwitch = "%name%, thanks for helping out the %toTeam% team!";
            YellDetectedGoodTeamSwitch = "Thanks for helping out the %toTeam% team!";
            ChatAfterUnswitching = "%name%, please stay on the %toTeam% team for the rest of this round";
            YellAfterUnswitching = "Please stay on the %toTeam% team for the rest of this round";

            /* ===== SECTION 6 - Unswitcher ===== */

            EnableImmediateUnswitch = true;
            ForbidSwitchingAfterAutobalance = UnswitchChoice.Always;
            ForbidSwitchingToWinningTeam = UnswitchChoice.Always;
            ForbidSwitchingToBiggestTeam = UnswitchChoice.Always;
            ForbidSwitchingAfterDispersal = UnswitchChoice.Always;

            /* ===== SECTION 7 - TBD ===== */

            /* ===== SECTION 8 - Per-Mode Settings ===== */

            /* ===== SECTION 9 - Debug Settings ===== */

            ShowInLog = INVALID_NAME_TAG_GUID;
            ShowCommandInLog = String.Empty;
            LogChat = true;
            EnableLoggingOnlyMode = false;
            EnableExternalLogging = false;
            ExternalLogSuffix = "_mb.log";
        }