Exemplo n.º 1
0
        private void SaveSummary(string filename)
        {
            const int CountPerSection = 20;

            var results = new List <MasteryStats>(Results);

            results.RemoveAll(r => r.GameName.Contains("[Bonus]") || r.GameName.Contains("[Multi]") || r.GameName.EndsWith(" (Events)"));

            DateTime thirtyDaysAgo = DateTime.Today - TimeSpan.FromDays(30);
            DateTime now           = DateTime.Now;

            var cheaters     = new List <CheaterInfo>();
            var cheatedGames = new List <CheatedGameInfo>();

            results.Sort((l, r) =>
            {
                if (l == null)
                {
                    return(-1);
                }
                if (r == null)
                {
                    return(1);
                }
                return(l.GameId - r.GameId);
            });

            var detailedUserMasteryInfo = new List <string>();

            Progress.Label = "Analyzing data...";
            Progress.Reset(results.Count);
            Progress.IsEnabled = true;
            foreach (var result in results)
            {
                ++Progress.Current;

                GameStatsViewModel gameStats = null;

                if (!String.IsNullOrEmpty(UserMasteryDetails))
                {
                    gameStats = new GameStatsViewModel()
                    {
                        GameId = result.GameId
                    };
                    gameStats.LoadGame();

                    GameStatsViewModel.UserStats userStats = null;
                    int userIndex     = -1;
                    int masteredCount = 0;
                    foreach (var user in gameStats.TopUsers)
                    {
                        if (user.PointsEarned < gameStats.TotalPoints)
                        {
                            break;
                        }

                        if (user.User == UserMasteryDetails)
                        {
                            userIndex = masteredCount;
                            userStats = user;
                        }

                        masteredCount++;
                    }

                    if (userStats != null)
                    {
                        detailedUserMasteryInfo.Add(String.Format("{2,3}/{3,3} | {0,4}m/{1,4}m | {4,3}x {5,6}:{6}",
                                                                  (int)userStats.GameTime.TotalMinutes, (int)result.MeanTimeToMaster,
                                                                  userIndex + 1, masteredCount,
                                                                  gameStats.Achievements.Count(), result.GameId, result.GameName));
                    }
                }

                if (result.HardcoreMasteredUserCount < 8 || result.Points < 50)
                {
                    continue;
                }

                var threshold = result.MeanTimeToMaster / 5;
                if (threshold > result.MeanTimeToMaster - result.StdDevTimeToMaster * 2)
                {
                    continue;
                }

                if (gameStats == null)
                {
                    gameStats = new GameStatsViewModel()
                    {
                        GameId = result.GameId
                    };
                    gameStats.LoadGame();
                }

                var             usersToRefresh = new List <GameStatsViewModel.UserStats>();
                CheatedGameInfo gameEntry      = null;

                foreach (var user in gameStats.TopUsers)
                {
                    if (user.PointsEarned == result.Points && user.GameTime.TotalMinutes < threshold)
                    {
                        // if the user isn't averaging at least three achievements per session, the
                        // estimate will be off. ignore it.
                        if (!user.IsEstimateReliable)
                        {
                            continue;
                        }

                        // some things that appear like cheating aren't. check the exceptions list.
                        if (IgnoreCheater(user, result.GameId))
                        {
                            continue;
                        }

                        // if the user data contains a bunch of entries without seconds, it's old. try refreshing it
                        if (user.Achievements.Count(a => a.Value.Second == 0) > gameStats.Achievements.Count() / 2)
                        {
                            usersToRefresh.Add(user);
                        }

                        // add a new cheating entry for the user
                        var entry = cheaters.FirstOrDefault(c => c.UserName == user.User);
                        if (entry == null)
                        {
                            entry = new CheaterInfo()
                            {
                                UserName = user.User,
                                Results  = new List <KeyValuePair <MasteryStats, GameStatsViewModel.UserStats> >()
                            };
                            cheaters.Add(entry);
                        }
                        entry.Results.Add(new KeyValuePair <MasteryStats, GameStatsViewModel.UserStats>(result, user));

                        // add a new cheating entry for the game
                        if (gameEntry == null)
                        {
                            gameEntry = new CheatedGameInfo()
                            {
                                Game  = result,
                                Users = new List <GameStatsViewModel.UserStats>()
                            };
                            cheatedGames.Add(gameEntry);
                        }
                        gameEntry.Users.Add(user);
                    }
                }

                if (usersToRefresh.Count > 0)
                {
                    gameStats.RefreshUsers(usersToRefresh);
                }
            }

            cheaters.Sort((l, r) =>
            {
                int diff = (r.Results.Count - l.Results.Count);
                if (diff == 0)
                {
                    diff = String.Compare(l.UserName, r.UserName);
                }
                return(diff);
            });

            using (var file = File.CreateText(filename))
            {
                file.WriteLine("Games:         {0,6:D}", Snapshot.GameCount);
                file.WriteLine("Achievements:  {0,6:D} ({1} games with achievements)", Snapshot.AchievementCount, Snapshot.AchievementGameCount);
                file.WriteLine("Leaderboards:  {0,6:D} ({1} games with leaderboards)", Snapshot.LeaderboardCount, Snapshot.LeaderboardGameCount);
                file.WriteLine("RichPresences: {0,6:D} ({1} static)", Snapshot.RichPresenceCount, Snapshot.StaticRichPresenceCount);
                file.WriteLine("Authors:       {0,6:D}", Snapshot.AuthorCount);
                file.WriteLine("Systems:       {0,6:D}", Snapshot.SystemCount);
                file.WriteLine();

                file.WriteLine("Most played: MAX(Players)");
                file.WriteLine("```");
                results.Sort((l, r) => l.NumPlayers - r.NumPlayers);
                for (int i = results.Count - 1, count = 0; count < CountPerSection; i--, count++)
                {
                    file.WriteLine(String.Format("{0,5:D} {1}", results[i].NumPlayers, results[i].GameName));
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Least played: MIN(Players) [Players > 0, Age > 30 days]");
                file.WriteLine("```");
                for (int i = 0, count = 0; count < CountPerSection || results[i].NumPlayers == results[i - 1].NumPlayers; i++)
                {
                    if (results[i].NumPlayers > 0 && results[i].Created < thirtyDaysAgo)
                    {
                        file.WriteLine(String.Format("{0,5:D} {1}", results[i].NumPlayers, results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Most Popular: MAX(Players/Day) [Age > 30 days]");
                file.WriteLine("```");
                results.Sort((l, r) => (int)((l.PlayersPerDay - r.PlayersPerDay) * 100000));
                for (int i = results.Count - 1, count = 0; count < CountPerSection; i--)
                {
                    if (results[i].NumPlayers > 0 && results[i].Created < thirtyDaysAgo)
                    {
                        file.WriteLine(String.Format("{0:F3} {1}", results[i].PlayersPerDay, results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Least Popular: MIN(Players/Day) [Age > 30 days]");
                file.WriteLine("```");
                for (int i = 0, count = 0; count < CountPerSection; i++)
                {
                    if (results[i].NumPlayers > 0 && results[i].Created < thirtyDaysAgo)
                    {
                        file.WriteLine(String.Format("{0:F4} {1}", results[i].PlayersPerDay, results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Slowest to Master: MAX(MeanTimeToMaster) MasteryRate|MeanTimeToMaster|StdDev [Players Mastered >= 3]");
                file.WriteLine("```");
                results.Sort((l, r) => (int)((l.MeanTimeToMaster - r.MeanTimeToMaster) * 100000));
                for (int i = results.Count - 1, count = 0; count < CountPerSection; i--)
                {
                    if (results[i].HardcoreMasteredUserCount >= 3)
                    {
                        file.WriteLine(String.Format("{0,4:D}/{1,4:D} {2,8:F2} {3,8:F2} {4}",
                                                     results[i].HardcoreMasteredUserCount, results[i].NumPlayers,
                                                     results[i].MeanTimeToMaster, results[i].StdDevTimeToMaster,
                                                     results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Fastest to Master: MIN(MeanTimeToMaster) MasteryRate|MeanTimeToMaster|StdDev [Players Mastered >= 3, Points >= 50]");
                file.WriteLine("```");
                for (int i = 0, count = 0; count < CountPerSection; i++)
                {
                    if (results[i].HardcoreMasteredUserCount >= 3 && results[i].Points >= 50 && results[i].MeanTimeToMaster > 0.0)
                    {
                        file.WriteLine(String.Format("{0,4:D}/{1,4:D} {2,8:F2} {3,8:F2} {4}",
                                                     results[i].HardcoreMasteredUserCount, results[i].NumPlayers,
                                                     results[i].MeanTimeToMaster, results[i].StdDevTimeToMaster,
                                                     results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Fastest to Master: MIN(MeanTimeToMaster) MasteryRate|MeanTimeToMaster|StdDev [Players Mastered >= 3, Points >= 400]");
                file.WriteLine("```");
                for (int i = 0, count = 0; count < CountPerSection; i++)
                {
                    if (results[i].HardcoreMasteredUserCount >= 3 && results[i].Points >= 400 && results[i].MeanTimeToMaster > 0.0)
                    {
                        file.WriteLine(String.Format("{0,4:D}/{1,4:D} {2,8:F2} {3,8:F2} {4}",
                                                     results[i].HardcoreMasteredUserCount, results[i].NumPlayers,
                                                     results[i].MeanTimeToMaster, results[i].StdDevTimeToMaster,
                                                     results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Points requiring the least effort: MIN(MinutesPerPoint)|NintiethPercentilePoints|Players [>= 2 achievements earned by 90% of players, Players >= 3]");
                file.WriteLine("```");
                results.Sort((l, r) =>
                {
                    if (l == null)
                    {
                        return(-1);
                    }
                    if (r == null)
                    {
                        return(1);
                    }

                    return((int)((l.MinutesPerPoint - r.MinutesPerPoint) * 100000));
                });
                for (int i = 0, count = 0; count < CountPerSection; i++)
                {
                    if (results[i].NintiethPercentileAchievements >= 3 && results[i].NumPlayers >= 3)
                    {
                        file.WriteLine(String.Format("{0,6:F3} {1,4:D} {2,4:D} {3}", results[i].MinutesPerPoint, results[i].NintiethPercentilePoints, results[i].NumPlayers, results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Points requiring the most effort: MAX(MinutesPerPoint)|NintiethPercentilePoints|Players [>= 10 points earned by 90% of players, Players >= 3]");
                file.WriteLine("```");
                for (int i = results.Count - 1, count = 0; count < CountPerSection; i--)
                {
                    if (results[i].NintiethPercentilePoints >= 10 && results[i].NumPlayers >= 3)
                    {
                        file.WriteLine(String.Format("{0,6:F3} {1,4:D} {2,4:D} {3}", results[i].MinutesPerPoint, results[i].NintiethPercentilePoints, results[i].NumPlayers, results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Easiest sets: MAX(NintiethPercentilePoints/Points)|Players [Players >= 10, Points >= 50]");
                file.WriteLine("```");
                results.Sort((l, r) =>
                {
                    if (l == null || l.Points == 0)
                    {
                        return(-1);
                    }
                    if (r == null || r.Points == 0)
                    {
                        return(1);
                    }
                    return(((l.NintiethPercentilePoints * 10000) / l.Points) - ((r.NintiethPercentilePoints * 10000) / r.Points));
                });
                for (int i = results.Count - 1, count = 0; count < CountPerSection; i--)
                {
                    if (results[i].NumPlayers >= 10 && results[i].Points >= 50)
                    {
                        file.WriteLine(String.Format("{0,4:D}/{1,4:D} {2,4:D} {3}",
                                                     results[i].NintiethPercentilePoints, results[i].Points, results[i].NumPlayers,
                                                     results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Easiest sets: MAX(NintiethPercentilePoints/Points)|Players [Players >= 10, Points >= 400]");
                file.WriteLine("```");
                for (int i = results.Count - 1, count = 0; count < CountPerSection; i--)
                {
                    if (results[i].NumPlayers >= 10 && results[i].Points >= 400)
                    {
                        file.WriteLine(String.Format("{0,4:D}/{1,4:D} {2,4:D} {3}",
                                                     results[i].NintiethPercentilePoints, results[i].Points, results[i].NumPlayers,
                                                     results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Hardest sets: MIN(TwentyFifthPercentilePoints/Points)|Players [Players >= 10, TwentyFifthPercentilePoints > 0]");
                file.WriteLine("```");
                results.Sort((l, r) =>
                {
                    if (l == null || l.Points == 0)
                    {
                        return(-1);
                    }
                    if (r == null || r.Points == 0)
                    {
                        return(1);
                    }
                    return((l.TwentyFifthPercentilePoints * 10000) / l.Points - (r.TwentyFifthPercentilePoints * 10000) / r.Points);
                });
                for (int i = 0, count = 0; count < CountPerSection; i++)
                {
                    if (results[i].NumPlayers >= 10 && results[i].TwentyFifthPercentilePoints > 0)
                    {
                        file.WriteLine(String.Format("{0,4:D}/{1,4:D} {2,4:D} {3}",
                                                     results[i].TwentyFifthPercentilePoints, results[i].Points, results[i].NumPlayers,
                                                     results[i].GameName));
                        count++;
                    }
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Most Earned Achievements: MAX(Players)|Achievement (Game)");
                file.WriteLine("```");
                foreach (var achievement in _mostAwardedAchievements)
                {
                    file.WriteLine(String.Format("{0,5:D} {1} ({2})",
                                                 achievement.EarnedBy, achievement.Title, achievement.Description));
                }
                file.WriteLine("```");
                file.WriteLine();

                file.WriteLine("Possible cheaters: TimeToMaster < 20% of median Time/Median/StdDev|LinkToComparePage [Masters >= 8, Points >= 50, TimeToMaster more than 2 stddevs from median]");
                file.WriteLine();
                foreach (var cheater in cheaters)
                {
                    foreach (var kvp in cheater.Results)
                    {
                        var result = kvp.Key;
                        var user   = kvp.Value;
                        file.WriteLine("* {0} mastered {1} ({2})", user.User, result.GameName, result.GameId);
                        file.WriteLine("  https://retroachievements.org/gamecompare.php?ID={0}&f={1}", result.GameId, user.User);

                        bool dumpTimes = true;

                        var notified = CheaterNotified(user.User, result.GameId);
                        if (!String.IsNullOrEmpty(notified))
                        {
                            file.WriteLine("  - notified {0}", notified);
                        }

                        if (IsUntracked(user.User))
                        {
                            file.WriteLine("  - currently Untracked");
                            dumpTimes = false;
                        }

                        var performance = 1.0 - (user.GameTime.TotalMinutes / result.MeanTimeToMaster);
                        file.WriteLine("  Time to Master: {0:F2} ({1:F2}% faster than median {2:F2}, std dev={3:F2})", user.GameTime.TotalMinutes, performance * 100, result.MeanTimeToMaster, result.StdDevTimeToMaster);

                        if (dumpTimes)
                        {
                            var achievements = new List <AchievementTime>();

                            foreach (var achievement in user.Achievements)
                            {
                                achievements.Add(new AchievementTime {
                                    Id = achievement.Key, When = achievement.Value
                                });
                            }
                            achievements.Sort((l, r) => DateTime.Compare(l.When, r.When));

                            var gameStats = new GameStatsViewModel()
                            {
                                GameId = result.GameId
                            };
                            gameStats.LoadGame();

                            foreach (var achievement in achievements)
                            {
                                file.Write("  {0:D4}-{1:D2}-{2:D2} {3:D2}:{4:D2}:{5:D2} ", achievement.When.Year, achievement.When.Month, achievement.When.Day,
                                           achievement.When.Hour, achievement.When.Minute, achievement.When.Second);

                                var achDef = gameStats.Achievements.FirstOrDefault(a => a.Id == achievement.Id);
                                if (achDef != null)
                                {
                                    file.WriteLine("{0,6:D} {1}", achDef.Id, achDef.Title);
                                }
                                else
                                {
                                    file.WriteLine("{0,6:D} ??????", achievement.Id);
                                }
                            }
                        }
                        file.WriteLine();
                    }
                }
                file.WriteLine();

                file.WriteLine("Most cheated games: Count|ID|Name [most frequent games from previous list]");
                file.WriteLine("```");
                cheatedGames.Sort((l, r) =>
                {
                    int diff = (r.Users.Count - l.Users.Count);
                    if (diff == 0)
                    {
                        diff = String.Compare(l.Game.GameName, r.Game.GameName);
                    }
                    return(diff);
                });
                for (int i = 0, count = 0; (count < CountPerSection && cheatedGames[i].Users.Count > 1) || (i > 0 && cheatedGames[i].Users.Count == cheatedGames[i - 1].Users.Count); i++)
                {
                    file.WriteLine(String.Format("{0,2:D} {1,5:D} {2}", cheatedGames[i].Users.Count,
                                                 cheatedGames[i].Game.GameId, cheatedGames[i].Game.GameName));
                }
                file.WriteLine("```");
                file.WriteLine();

                if (!String.IsNullOrEmpty(UserMasteryDetails))
                {
                    file.Write("Details for ");
                    file.WriteLine(UserMasteryDetails);
                    file.WriteLine("  rank  |   mastery   | achs gameid:name");
                    file.WriteLine(" ------ | ----------- | -----------------------------------------------------------------------");

                    detailedUserMasteryInfo.Sort();
                    foreach (var line in detailedUserMasteryInfo)
                    {
                        file.WriteLine(line);
                    }
                }
            }

            Progress.Label = String.Empty;
        }
Exemplo n.º 2
0
        private static bool IgnoreCheater(GameStatsViewModel.UserStats user, int gameId)
        {
            switch (user.User)
            {
            case "AgentRibinski":
                // seems like a bad estimation caused by playing over many days
                return(gameId == 669);

            case "Agrahnax":
                // seems like a bad estimation caused by playing over many days
                return(gameId == 676);

            case "amine456":
                // televandalist seems to think the times seem reasonable
                // https://discord.com/channels/310192285306454017/564271682731245603/794743956948647936
                return(gameId == 535);

            case "Baobabastass":
                // KickMeElmo indicates the game has bugs which allow endgame equipment early
                // https://discord.com/channels/310192285306454017/564271682731245603/794680166306676766
                return(gameId == 762);

            case "boxmeister":
                // suspicious achievements were all added after the bulk of the achievements were unlocked.
                // suspect he completed the game normally, then unlocked the new achievements with his existing save
                return(gameId == 802);

            case "chro":
                // two rapid sessions with a big gap in the middle
                // https://discord.com/channels/310192285306454017/564271682731245603/794635369555034142
                return(gameId == 788);

            case "joker1000":
                // golden sun was broken
                // https://discord.com/channels/310192285306454017/564271682731245603/826985751379050566
                if (gameId == 2592)
                {
                    return(true);
                }
                break;

            case "Nevermond12":
                // he's just that good
                // https://discord.com/channels/310192285306454017/564271682731245603/826985306074120202
                return(gameId == 10173);

            case "Riger":
                // Salsa manually unlocked a bunch of stuff for Riger on 4/14/2018: https://discord.com/channels/310192285306454017/360584144281010178/434742993728307201
                if (user.Achievements.First().Value.Year == 2018)
                {
                    return(true);
                }
                break;

            case "Valenstein":
                // seems like a bad estimation caused by playing over many days
                return(gameId == 782);

            case "VICTORKRATOS":
                // likely offline/reconnect unlocks
                // https://discord.com/channels/310192285306454017/564271682731245603/794620459374477362
                return(gameId == 624 || gameId == 1485);
            }

            return(false);
        }