private int LoadAchievementStatsFromServer(string gamePage, List <AchievementStats> allStats, out AchievementStats mostWon, out AchievementStats leastWon) { int totalPoints = 0; var tokenizer = Tokenizer.CreateTokenizer(gamePage); tokenizer.ReadTo("<title>"); if (tokenizer.Match("<title>")) { var title = tokenizer.ReadTo("</title>"); var titleString = title.ToString(); var index = title.IndexOf("RetroAchievements", StringComparison.OrdinalIgnoreCase); if (index != -1) { var length = 17; if (index > 3 && title.SubToken(index - 3, 3) == " - ") { index -= 3; length += 3; } if (index + length < title.Length - 4 && title.SubToken(index + length, 4) == ".org") { length += 4; } if (index + length < title.Length - 3 && title.SubToken(index + length, 3) == " - ") { length += 3; } titleString = titleString.Substring(0, index) + titleString.Substring(index + length); } _gameName = titleString; } mostWon = null; leastWon = null; totalPoints = 0; do { tokenizer.ReadTo("<div class='achievemententry'>"); if (tokenizer.NextChar == '\0') { break; } AchievementStats stats = new AchievementStats(); tokenizer.ReadTo("won by "); tokenizer.Advance(7); if (tokenizer.NextChar != '-') // ignore buggy value "won by -200 (300) of 400" { var winners = tokenizer.ReadNumber(); stats.EarnedBy = Int32.Parse(winners.ToString()); } if (stats.EarnedBy > 0) { tokenizer.SkipWhitespace(); if (tokenizer.NextChar == '<') { tokenizer.ReadTo('>'); tokenizer.Advance(); tokenizer.SkipWhitespace(); } if (tokenizer.NextChar == '(') { tokenizer.Advance(); var hardcoreWinners = tokenizer.ReadNumber(); stats.EarnedHardcoreBy = Int32.Parse(hardcoreWinners.ToString()); } } if (NumberOfPlayers == 0) { tokenizer.ReadTo("of "); tokenizer.Advance(3); var players = tokenizer.ReadNumber(); NumberOfPlayers = Int32.Parse(players.ToString()); } tokenizer.ReadTo("<a href='/achievement/"); if (tokenizer.Match("<a href='/achievement/")) { var achievementId = tokenizer.ReadTo("'>"); stats.Id = Int32.Parse(achievementId.ToString()); tokenizer.Advance(2); var achievementTitle = tokenizer.ReadTo("</a>").TrimRight(); Token achievementPoints = Token.Empty; if (achievementTitle.EndsWith(")")) { for (int i = achievementTitle.Length - 1; i >= 0; i--) { if (achievementTitle[i] == '(') { achievementPoints = achievementTitle.SubToken(i + 1, achievementTitle.Length - i - 2); achievementTitle = achievementTitle.SubToken(0, i); break; } } } stats.Title = achievementTitle.TrimRight().ToString(); int points; if (Int32.TryParse(achievementPoints.ToString(), out points)) { stats.Points = points; } tokenizer.ReadTo("<br>"); tokenizer.Advance(4); var achievementDescription = tokenizer.ReadTo("<br>"); stats.Description = achievementDescription.Trim().ToString(); } allStats.Add(stats); totalPoints += stats.Points; if (mostWon == null) { mostWon = leastWon = stats; } else { if (stats.EarnedHardcoreBy > mostWon.EarnedHardcoreBy) { mostWon = stats; } else if (stats.EarnedHardcoreBy == mostWon.EarnedHardcoreBy && stats.EarnedBy > mostWon.EarnedBy) { mostWon = stats; } if (stats.EarnedHardcoreBy < leastWon.EarnedHardcoreBy) { leastWon = stats; } else if (stats.EarnedHardcoreBy == leastWon.EarnedHardcoreBy && stats.EarnedBy < leastWon.EarnedBy) { leastWon = stats; } } } while (true); return(totalPoints); }
internal void LoadGame() { var gamePage = RAWebCache.Instance.GetGamePage(GameId); if (gamePage == null) { return; } var tokenizer = Tokenizer.CreateTokenizer(gamePage); tokenizer.ReadTo("<title>"); if (tokenizer.Match("<title>")) { var title = tokenizer.ReadTo("</title>"); var titleString = title.ToString(); var index = title.IndexOf("RetroAchievements", StringComparison.OrdinalIgnoreCase); if (index != -1) { var length = 17; if (index > 3 && title.SubToken(index - 3, 3) == " - ") { index -= 3; length += 3; } if (index + length < title.Length - 4 && title.SubToken(index + length, 4) == ".org") { length += 4; } if (index + length < title.Length - 3 && title.SubToken(index + length, 3) == " - ") { length += 3; } titleString = titleString.Substring(0, index) + titleString.Substring(index + length); } DialogTitle = "Game Stats - " + titleString; } AchievementStats mostWon = null; AchievementStats leastWon = null; var totalPoints = 0; var allStats = new List <AchievementStats>(); do { tokenizer.ReadTo("<div class='achievemententry'>"); if (tokenizer.NextChar == '\0') { break; } AchievementStats stats = new AchievementStats(); tokenizer.ReadTo("won by "); tokenizer.Advance(7); var winners = tokenizer.ReadNumber(); stats.EarnedBy = Int32.Parse(winners.ToString()); if (stats.EarnedBy > 0) { tokenizer.SkipWhitespace(); if (tokenizer.NextChar == '(') { tokenizer.Advance(); var hardcoreWinners = tokenizer.ReadNumber(); stats.EarnedHardcoreBy = Int32.Parse(hardcoreWinners.ToString()); } } if (NumberOfPlayers == 0) { tokenizer.ReadTo("of "); tokenizer.Advance(3); var players = tokenizer.ReadNumber(); NumberOfPlayers = Int32.Parse(players.ToString()); } tokenizer.ReadTo("<a href='/Achievement/"); if (tokenizer.Match("<a href='/Achievement/")) { var achievementId = tokenizer.ReadTo("'>"); stats.Id = Int32.Parse(achievementId.ToString()); tokenizer.Advance(2); var achievementTitle = tokenizer.ReadTo("</a>"); Token achievementPoints = Token.Empty; for (int i = achievementTitle.Length - 1; i >= 0; i--) { if (achievementTitle[i] == '(') { achievementPoints = achievementTitle.SubToken(i + 1, achievementTitle.Length - i - 2); achievementTitle = achievementTitle.SubToken(0, i); break; } } stats.Title = achievementTitle.ToString(); stats.Points = Int32.Parse(achievementPoints.ToString()); } allStats.Add(stats); totalPoints += stats.Points; if (mostWon == null) { mostWon = leastWon = stats; } else { if (stats.EarnedHardcoreBy > mostWon.EarnedHardcoreBy) { mostWon = stats; } else if (stats.EarnedHardcoreBy == mostWon.EarnedHardcoreBy && stats.EarnedBy > mostWon.EarnedBy) { mostWon = stats; } if (stats.EarnedHardcoreBy < leastWon.EarnedHardcoreBy) { leastWon = stats; } else if (stats.EarnedHardcoreBy == leastWon.EarnedHardcoreBy && stats.EarnedBy < leastWon.EarnedBy) { leastWon = stats; } } } while (true); var masteryPoints = (totalPoints * 2).ToString(); TotalPoints = totalPoints; var masters = new List <string>(); tokenizer = Tokenizer.CreateTokenizer(gamePage); tokenizer.ReadTo("<h3>High Scores</h3>"); do { tokenizer.ReadTo("<td class='user'>"); if (tokenizer.NextChar == '\0') { break; } tokenizer.ReadTo("<a href='"); tokenizer.ReadTo('>'); tokenizer.Advance(); var userName = tokenizer.ReadTo('<'); tokenizer.ReadTo("<span"); tokenizer.ReadTo('>'); tokenizer.Advance(); var points = tokenizer.ReadTo('<'); if (points != masteryPoints) { break; } masters.Add(userName.ToString()); } while (true); Progress.Label = "Fetching user stats"; Progress.Reset(allStats.Count); allStats.Sort((l, r) => { var diff = r.EarnedHardcoreBy - l.EarnedHardcoreBy; if (diff == 0) { diff = String.Compare(l.Title, r.Title, StringComparison.OrdinalIgnoreCase); } return(diff); }); Achievements = allStats; var nonHardcoreUsers = new List <string>(); var userStats = new List <UserStats>(); foreach (var achievement in allStats) { var achievementPage = RAWebCache.Instance.GetAchievementPage(achievement.Id); if (achievementPage != null) { tokenizer = Tokenizer.CreateTokenizer(achievementPage); tokenizer.ReadTo("<h3>Winners</h3>"); // NOTE: this only lists the ~50 most recent unlocks! For games with more than 50 users who have mastered it, the oldest may be missed! do { tokenizer.ReadTo("<a href='/User/"); if (tokenizer.NextChar == '\0') { break; } tokenizer.ReadTo("'>"); tokenizer.Advance(2); var user = tokenizer.ReadTo("</a>"); if (user.StartsWith("<img")) { continue; } var mid = tokenizer.ReadTo("<small>"); if (mid.Contains("Hardcore!")) { tokenizer.Advance(7); var when = tokenizer.ReadTo("</small>"); var date = DateTime.Parse(when.ToString()); date = DateTime.SpecifyKind(date, DateTimeKind.Utc); var stats = new UserStats { User = user.ToString() }; var index = userStats.BinarySearch(stats, stats); if (index < 0) { userStats.Insert(~index, stats); } else { stats = userStats[index]; } stats.Achievements[achievement.Id] = date; stats.PointsEarned += achievement.Points; if (ReferenceEquals(achievement, leastWon)) { if (!masters.Contains(stats.User)) { masters.Add(stats.User); } } } else { if (!nonHardcoreUsers.Contains(user.ToString())) { nonHardcoreUsers.Add(user.ToString()); } } } while (true); } Progress.Current++; } // if more than 50 people have earned achievements, people who mastered the game early may no longer display // in the individual pages. fetch mastery data by user if (mostWon.EarnedBy > 50) { HardcoreMasteredUserCountEstimated = (leastWon.EarnedBy > 50); Progress.Reset(masters.Count); foreach (var user in masters) { Progress.Current++; var masteryJson = RAWebCache.Instance.GetUserGameMasteryJson(user, GameId); if (masteryJson == null) // not able to get - probably not logged in. don't try other users { break; } var stats = new UserStats { User = user }; var index = userStats.BinarySearch(stats, stats); if (index < 0) { userStats.Insert(~index, stats); } else { stats = userStats[index]; } if (stats.PointsEarned == totalPoints) { continue; } var achievements = masteryJson.GetField("achievements").ObjectValue; foreach (var achievement in achievements) { var id = Int32.Parse(achievement.FieldName); if (!stats.Achievements.ContainsKey(id)) { var dateField = achievement.ObjectValue.GetField("DateEarnedHardcore"); if (dateField.Type != Jamiras.IO.Serialization.JsonFieldType.String) { dateField = achievement.ObjectValue.GetField("DateEarned"); } if (dateField.Type == Jamiras.IO.Serialization.JsonFieldType.String) { var date = DateTime.Parse(dateField.StringValue); date = DateTime.SpecifyKind(date, DateTimeKind.Utc); stats.Achievements[id] = date; stats.PointsEarned += Int32.Parse(achievement.ObjectValue.GetField("Points").StringValue); } } } } } else { HardcoreMasteredUserCountEstimated = false; } Progress.Label = "Analyzing data"; var sessions = new List <int>(userStats.Count); var days = new List <int>(userStats.Count); var idleTime = TimeSpan.FromHours(4); foreach (var user in userStats) { var times = new List <DateTime>(user.Achievements.Count); foreach (var achievement in user.Achievements) { times.Add(achievement.Value); } times.Sort((l, r) => (int)((l - r).TotalSeconds)); user.RealTime = times[times.Count - 1] - times[0]; int start = 0, end = 0; while (end < times.Count) { if (end + 1 == times.Count || (times[end + 1] - times[end]) >= idleTime) { user.Sessions++; user.GameTime += times[end] - times[start]; start = end + 1; } end++; } // assume every achievement took roughly the same amount of time to earn. divide the user's total known playtime // by the number of achievements they've earned to get the approximate time per achievement earned. add this value // to each session to account for time played before getting the first achievement of the session and time played // after gettin the last achievement of the session. double perSessionAdjustment = user.GameTime.TotalSeconds / user.Achievements.Count; user.GameTime += TimeSpan.FromSeconds(user.Sessions * perSessionAdjustment); if (user.PointsEarned == totalPoints) { sessions.Add(user.Sessions); days.Add((int)Math.Ceiling(user.RealTime.TotalDays)); } } userStats.Sort((l, r) => { var diff = r.PointsEarned - l.PointsEarned; if (diff == 0) { diff = (int)((l.GameTime - r.GameTime).TotalSeconds); } return(diff); }); HardcoreUserCount = userStats.Count; NonHardcoreUserCount = nonHardcoreUsers.Count; MedianHardcoreUserScore = userStats.Count > 0 ? userStats[userStats.Count / 2].PointsEarned : 0; int masteredCount = sessions.Count; if (masteredCount > 0) { HardcoreMasteredUserCount = masteredCount; var timeToMaster = masteredCount > 0 ? userStats[masteredCount / 2].Summary : "n/a"; var space = timeToMaster.IndexOf(' '); if (space > 0) { timeToMaster = timeToMaster.Substring(0, space); } MedianTimeToMaster = timeToMaster; sessions.Sort(); MedianSessionsToMaster = sessions[masteredCount / 2].ToString(); days.Sort(); MedianDaysToMaster = days[masteredCount / 2].ToString(); } if (userStats.Count > 100) { userStats.RemoveRange(100, userStats.Count - 100); } TopUsers = userStats; Progress.Label = String.Empty; }
private bool LoadGameFromFile(List <AchievementStats> achievementStats, List <UserStats> userStats, bool allowOldData) { var file = Path.Combine(_settings.DumpDirectory, GameId + ".txt"); if (!_fileSystem.FileExists(file)) { return(false); } TotalPoints = 0; using (var reader = new StreamReader(_fileSystem.OpenFile(file, OpenFileMode.Read))) { while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line.StartsWith("NonHardCoreCaptured=")) { NonHardcoreUserCount = Int32.Parse(line.Substring(20)); continue; } if (line.StartsWith("NumberOfPlayers=")) { NumberOfPlayers = Int32.Parse(line.Substring(16)); continue; } if (line.StartsWith("Game=")) { _gameName = line.Substring(5); continue; } if (line.StartsWith("Achievement:")) { var stats = new AchievementStats(); var index = line.IndexOf(';'); var parts = line.Substring(0, index).Split(':', '=', '/'); stats.Id = Int32.Parse(parts[1]); stats.EarnedHardcoreBy = Int32.Parse(parts[2]); stats.EarnedBy = Int32.Parse(parts[3]); stats.Points = Int32.Parse(parts[4]); stats.Title = line.Substring(index + 1); achievementStats.Add(stats); TotalPoints += stats.Points; while (!reader.EndOfStream) { line = reader.ReadLine(); if (line.Length < 1) { break; } parts = line.Split('@'); var user = new UserStats { User = parts[0] }; index = userStats.BinarySearch(user, user); if (index < 0) { userStats.Insert(~index, user); } else { user = userStats[index]; } user.Achievements[stats.Id] = DateTime.Parse(parts[1]); } } } } return(true); }