public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { var ruleset = rulesets.GetRuleset(OnlineRulesetID); var mods = Mods != null?ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty <Mod>(); var scoreInfo = new ScoreInfo { TotalScore = TotalScore, MaxCombo = MaxCombo, User = User, Accuracy = Accuracy, OnlineScoreID = OnlineScoreID, Date = Date, PP = PP, Beatmap = Beatmap, RulesetID = OnlineRulesetID, Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, Mods = mods, IsLegacyScore = true }; if (Statistics != null) { foreach (var kvp in Statistics) { switch (kvp.Key) { case @"count_geki": scoreInfo.SetCountGeki(kvp.Value); break; case @"count_300": scoreInfo.SetCount300(kvp.Value); break; case @"count_katu": scoreInfo.SetCountKatu(kvp.Value); break; case @"count_100": scoreInfo.SetCount100(kvp.Value); break; case @"count_50": scoreInfo.SetCount50(kvp.Value); break; case @"count_miss": scoreInfo.SetCountMiss(kvp.Value); break; } } } return(scoreInfo); }
public Score Parse(Stream stream) { var score = new Score { Replay = new Replay() }; WorkingBeatmap workingBeatmap; using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); var scoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; score.ScoreInfo = scoreInfo; int version = sr.ReadInt32(); workingBeatmap = GetBeatmap(sr.ReadString()); if (workingBeatmap is DummyWorkingBeatmap) { throw new BeatmapNotFoundException(); } scoreInfo.User = new APIUser { Username = sr.ReadString() }; // MD5Hash sr.ReadString(); scoreInfo.SetCount300(sr.ReadUInt16()); scoreInfo.SetCount100(sr.ReadUInt16()); scoreInfo.SetCount50(sr.ReadUInt16()); scoreInfo.SetCountGeki(sr.ReadUInt16()); scoreInfo.SetCountKatu(sr.ReadUInt16()); scoreInfo.SetCountMiss(sr.ReadUInt16()); scoreInfo.TotalScore = sr.ReadInt32(); scoreInfo.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ sr.ReadBoolean(); scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); // lazer replays get a really high version number. if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) { scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod <ModClassic>()).ToArray(); } currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo; /* score.HpGraphString = */ sr.ReadString(); scoreInfo.Date = sr.ReadDateTime(); byte[] compressedReplay = sr.ReadByteArray(); if (version >= 20140721) { scoreInfo.OnlineID = sr.ReadInt64(); } else if (version >= 20121008) { scoreInfo.OnlineID = sr.ReadInt32(); } if (compressedReplay?.Length > 0) { using (var replayInStream = new MemoryStream(compressedReplay)) { byte[] properties = new byte[5]; if (replayInStream.Read(properties, 0, 5) != 5) { throw new IOException("input .lzma is too short"); } long outSize = 0; for (int i = 0; i < 8; i++) { int v = replayInStream.ReadByte(); if (v < 0) { throw new IOException("Can't Read 1"); } outSize |= (long)(byte)v << (8 * i); } long compressedSize = replayInStream.Length - replayInStream.Position; using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) using (var reader = new StreamReader(lzma)) readLegacyReplay(score.Replay, reader); } } } PopulateAccuracy(score.ScoreInfo); // before returning for database import, we must restore the database-sourced BeatmapInfo. // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo; return(score); }
public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { var ruleset = rulesets.GetRuleset(OnlineRulesetID); var rulesetInstance = ruleset.CreateInstance(); var mods = Mods != null?Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty <Mod>(); // all API scores provided by this class are considered to be legacy. mods = mods.Append(rulesetInstance.CreateMod <ModClassic>()).ToArray(); var scoreInfo = new ScoreInfo { TotalScore = TotalScore, MaxCombo = MaxCombo, User = User, Accuracy = Accuracy, OnlineScoreID = OnlineScoreID, Date = Date, PP = PP, BeatmapInfo = BeatmapInfo, RulesetID = OnlineRulesetID, Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, Mods = mods, }; if (Statistics != null) { foreach (var kvp in Statistics) { switch (kvp.Key) { case @"count_geki": scoreInfo.SetCountGeki(kvp.Value); break; case @"count_300": scoreInfo.SetCount300(kvp.Value); break; case @"count_katu": scoreInfo.SetCountKatu(kvp.Value); break; case @"count_100": scoreInfo.SetCount100(kvp.Value); break; case @"count_50": scoreInfo.SetCount50(kvp.Value); break; case @"count_miss": scoreInfo.SetCountMiss(kvp.Value); break; } } } return(scoreInfo); }
public override void Execute() { var displayPlays = new List <UserPlayInfo>(); var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? 0); var rulesetApiName = LegacyHelper.GetRulesetShortNameFromId(Ruleset ?? 0); Console.WriteLine("Getting user data..."); dynamic userData = GetJsonFromApi($"users/{ProfileName}/{rulesetApiName}"); Console.WriteLine("Getting user top scores..."); foreach (var play in GetJsonFromApi($"users/{userData.id}/scores/best?mode={rulesetApiName}&limit=100")) { var working = ProcessorWorkingBeatmap.FromFileOrId((string)play.beatmap.id); var modsAcronyms = ((JArray)play.mods).Select(x => x.ToString()).ToArray(); Mod[] mods = ruleset.CreateAllMods().Where(m => modsAcronyms.Contains(m.Acronym)).ToArray(); var scoreInfo = new ScoreInfo(working.BeatmapInfo, ruleset.RulesetInfo) { TotalScore = play.score, MaxCombo = play.max_combo, Mods = mods, Statistics = new Dictionary <HitResult, int>() }; scoreInfo.SetCount300((int)play.statistics.count_300); scoreInfo.SetCountGeki((int)play.statistics.count_geki); scoreInfo.SetCount100((int)play.statistics.count_100); scoreInfo.SetCountKatu((int)play.statistics.count_katu); scoreInfo.SetCount50((int)play.statistics.count_50); scoreInfo.SetCountMiss((int)play.statistics.count_miss); var score = new ProcessorScoreDecoder(working).Parse(scoreInfo); var difficultyCalculator = ruleset.CreateDifficultyCalculator(working); var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(ruleset, scoreInfo.Mods).ToArray()); var performanceCalculator = ruleset.CreatePerformanceCalculator(); var ppAttributes = performanceCalculator?.Calculate(score.ScoreInfo, difficultyAttributes); var thisPlay = new UserPlayInfo { Beatmap = working.BeatmapInfo, LocalPP = ppAttributes?.Total ?? 0, LivePP = play.pp, Mods = scoreInfo.Mods.Select(m => m.Acronym).ToArray(), MissCount = play.statistics.count_miss, Accuracy = scoreInfo.Accuracy * 100, Combo = play.max_combo, MaxCombo = difficultyAttributes.MaxCombo }; displayPlays.Add(thisPlay); } var localOrdered = displayPlays.OrderByDescending(p => p.LocalPP).ToList(); var liveOrdered = displayPlays.OrderByDescending(p => p.LivePP).ToList(); int index = 0; double totalLocalPP = localOrdered.Sum(play => Math.Pow(0.95, index++) * play.LocalPP); double totalLivePP = userData.statistics.pp; index = 0; double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play.LivePP); //todo: implement properly. this is pretty damn wrong. var playcountBonusPP = (totalLivePP - nonBonusLivePP); totalLocalPP += playcountBonusPP; double totalDiffPP = totalLocalPP - totalLivePP; if (OutputJson) { var json = JsonConvert.SerializeObject(new { Username = userData.username, LivePp = totalLivePP, LocalPp = totalLocalPP, PlaycountPp = playcountBonusPP, Scores = localOrdered.Select(item => new { BeatmapId = item.Beatmap.OnlineID, BeatmapName = item.Beatmap.ToString(), item.Combo, item.Accuracy, item.MissCount, item.Mods, LivePp = item.LivePP, LocalPp = item.LocalPP, PositionChange = liveOrdered.IndexOf(item) - localOrdered.IndexOf(item) }) }); Console.Write(json); if (OutputFile != null) { File.WriteAllText(OutputFile, json); } } else { OutputDocument(new Document( new Span($"User: {userData.username}"), "\n", new Span($"Live PP: {totalLivePP:F1} (including {playcountBonusPP:F1}pp from playcount)"), "\n", new Span($"Local PP: {totalLocalPP:F1} ({totalDiffPP:+0.0;-0.0;-})"), "\n", new Grid { Columns = { GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto }, Children = { new Cell("#"), new Cell("beatmap"), new Cell("max combo"), new Cell("accuracy"), new Cell("misses"), new Cell("mods"), new Cell("live pp"), new Cell("local pp"), new Cell("pp change"), new Cell("position change"), localOrdered.Select(item => new[] { new Cell($"{localOrdered.IndexOf(item) + 1}"), new Cell($"{item.Beatmap.OnlineID} - {item.Beatmap}"), new Cell($"{item.Combo}/{item.MaxCombo}x") { Align = Align.Right }, new Cell($"{Math.Round(item.Accuracy, 2)}%") { Align = Align.Right }, new Cell($"{item.MissCount}") { Align = Align.Right }, new Cell($"{(item.Mods.Length > 0 ? string.Join(", ", item.Mods) : "None")}") { Align = Align.Right }, new Cell($"{item.LivePP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP - item.LivePP:F1}") { Align = Align.Right }, new Cell($"{liveOrdered.IndexOf(item) - localOrdered.IndexOf(item):+0;-0;-}") { Align = Align.Center }, }) } }) ); } }
public Score Parse(Stream stream) { var score = new Score { ScoreInfo = new ScoreInfo(), Replay = new Replay() }; using (SerializationReader sr = new SerializationReader(stream)) { currentRuleset = GetRuleset(sr.ReadByte()); var scoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; score.ScoreInfo = scoreInfo; var version = sr.ReadInt32(); var workingBeatmap = GetBeatmap(sr.ReadString()); if (workingBeatmap is DummyWorkingBeatmap) { throw new BeatmapNotFoundException(); } currentBeatmap = workingBeatmap.Beatmap; scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; scoreInfo.User = new User { Username = sr.ReadString() }; // MD5Hash sr.ReadString(); scoreInfo.SetCount300(sr.ReadUInt16()); scoreInfo.SetCount100(sr.ReadUInt16()); scoreInfo.SetCount50(sr.ReadUInt16()); scoreInfo.SetCountGeki(sr.ReadUInt16()); scoreInfo.SetCountKatu(sr.ReadUInt16()); scoreInfo.SetCountMiss(sr.ReadUInt16()); scoreInfo.TotalScore = sr.ReadInt32(); scoreInfo.MaxCombo = sr.ReadUInt16(); /* score.Perfect = */ sr.ReadBoolean(); scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); /* score.HpGraphString = */ sr.ReadString(); scoreInfo.Date = sr.ReadDateTime(); var compressedReplay = sr.ReadByteArray(); if (version >= 20140721) { scoreInfo.OnlineScoreID = sr.ReadInt64(); } else if (version >= 20121008) { scoreInfo.OnlineScoreID = sr.ReadInt32(); } if (scoreInfo.OnlineScoreID <= 0) { scoreInfo.OnlineScoreID = null; } if (compressedReplay?.Length > 0) { using (var replayInStream = new MemoryStream(compressedReplay)) { byte[] properties = new byte[5]; if (replayInStream.Read(properties, 0, 5) != 5) { throw new IOException("input .lzma is too short"); } long outSize = 0; for (int i = 0; i < 8; i++) { int v = replayInStream.ReadByte(); if (v < 0) { throw new IOException("Can't Read 1"); } outSize |= (long)(byte)v << (8 * i); } long compressedSize = replayInStream.Length - replayInStream.Position; using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) using (var reader = new StreamReader(lzma)) readLegacyReplay(score.Replay, reader); } } } CalculateAccuracy(score.ScoreInfo); return(score); }
public override void Execute() { var rulesetApiName = LegacyHelper.GetRulesetShortNameFromId(Ruleset ?? 0); var leaderboard = GetJsonFromApi($"rankings/{rulesetApiName}/performance?cursor[page]={LeaderboardPage - 1}"); var calculatedPlayers = new List <LeaderboardPlayerInfo>(); foreach (var player in leaderboard.ranking) { if (calculatedPlayers.Count >= Limit) { break; } var plays = new List <(double, double)>(); // (local, live) var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? 0); Console.WriteLine($"Calculating {player.user.username} top scores..."); foreach (var play in GetJsonFromApi($"users/{player.user.id}/scores/best?mode={rulesetApiName}&limit=100")) { var working = ProcessorWorkingBeatmap.FromFileOrId((string)play.beatmap.id); var modsAcronyms = ((JArray)play.mods).Select(x => x.ToString()).ToArray(); Mod[] mods = ruleset.CreateAllMods().Where(m => modsAcronyms.Contains(m.Acronym)).ToArray(); var scoreInfo = new ScoreInfo(working.BeatmapInfo, ruleset.RulesetInfo) { TotalScore = play.score, MaxCombo = play.max_combo, Mods = mods, Statistics = new Dictionary <HitResult, int>() }; scoreInfo.SetCount300((int)play.statistics.count_300); scoreInfo.SetCountGeki((int)play.statistics.count_geki); scoreInfo.SetCount100((int)play.statistics.count_100); scoreInfo.SetCountKatu((int)play.statistics.count_katu); scoreInfo.SetCount50((int)play.statistics.count_50); scoreInfo.SetCountMiss((int)play.statistics.count_miss); var score = new ProcessorScoreDecoder(working).Parse(scoreInfo); var difficultyCalculator = ruleset.CreateDifficultyCalculator(working); var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(ruleset, scoreInfo.Mods).ToArray()); var performanceCalculator = ruleset.CreatePerformanceCalculator(); plays.Add((performanceCalculator?.Calculate(score.ScoreInfo, difficultyAttributes).Total ?? 0, play.pp)); } var localOrdered = plays.Select(x => x.Item1).OrderByDescending(x => x).ToList(); var liveOrdered = plays.Select(x => x.Item2).OrderByDescending(x => x).ToList(); int index = 0; double totalLocalPP = localOrdered.Sum(play => Math.Pow(0.95, index++) * play); double totalLivePP = player.pp; index = 0; double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play); //todo: implement properly. this is pretty damn wrong. var playcountBonusPP = (totalLivePP - nonBonusLivePP); totalLocalPP += playcountBonusPP; calculatedPlayers.Add(new LeaderboardPlayerInfo { LivePP = totalLivePP, LocalPP = totalLocalPP, Username = player.user.username }); } calculatedPlayers = calculatedPlayers.OrderByDescending(x => x.LocalPP).ToList(); var liveOrderedPlayers = calculatedPlayers.OrderByDescending(x => x.LivePP).ToList(); if (OutputJson) { var json = JsonConvert.SerializeObject(calculatedPlayers); Console.Write(json); if (OutputFile != null) { File.WriteAllText(OutputFile, json); } } else { OutputDocument(new Document( new Grid { Columns = { GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto }, Children = { new Cell("#"), new Cell("username"), new Cell("live pp"), new Cell("local pp"), new Cell("pp change"), calculatedPlayers.Select(item => new[] { new Cell($"{liveOrderedPlayers.IndexOf(item) - calculatedPlayers.IndexOf(item):+0;-0;-}"), new Cell($"{item.Username}"), new Cell($"{item.LivePP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP - item.LivePP:F1}") { Align = Align.Right } }) } }) ); } }
public override void Execute() { var displayPlays = new List<UserPlayInfo>(); var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? 0); Console.WriteLine("Getting user data..."); dynamic userData = getJsonFromApi($"get_user?k={Key}&u={ProfileName}&m={Ruleset}")[0]; Console.WriteLine("Getting user top scores..."); foreach (var play in getJsonFromApi($"get_user_best?k={Key}&u={ProfileName}&m={Ruleset}&limit=100")) { string beatmapID = play.beatmap_id; string cachePath = Path.Combine("cache", $"{beatmapID}.osu"); if (!File.Exists(cachePath)) { Console.WriteLine($"Downloading {beatmapID}.osu..."); new FileWebRequest(cachePath, $"{base_url}/osu/{beatmapID}").Perform(); } Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)play.enabled_mods).ToArray(); var working = new ProcessorWorkingBeatmap(cachePath, (int)play.beatmap_id); var scoreInfo = new ScoreInfo { Ruleset = ruleset.RulesetInfo, TotalScore = play.score, MaxCombo = play.maxcombo, Mods = mods, Statistics = new Dictionary<HitResult, int>() }; scoreInfo.SetCount300((int)play.count300); scoreInfo.SetCountGeki((int)play.countgeki); scoreInfo.SetCount100((int)play.count100); scoreInfo.SetCountKatu((int)play.countkatu); scoreInfo.SetCount50((int)play.count50); scoreInfo.SetCountMiss((int)play.countmiss); var score = new ProcessorScoreDecoder(working).Parse(scoreInfo); var thisPlay = new UserPlayInfo { Beatmap = working.BeatmapInfo, LocalPP = ruleset.CreatePerformanceCalculator(working, score.ScoreInfo).Calculate(), LivePP = play.pp, Mods = mods.Length > 0 ? mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}") : "None" }; displayPlays.Add(thisPlay); } var localOrdered = displayPlays.OrderByDescending(p => p.LocalPP).ToList(); var liveOrdered = displayPlays.OrderByDescending(p => p.LivePP).ToList(); int index = 0; double totalLocalPP = localOrdered.Sum(play => Math.Pow(0.95, index++) * play.LocalPP); double totalLivePP = userData.pp_raw; index = 0; double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play.LivePP); //todo: implement properly. this is pretty damn wrong. var playcountBonusPP = (totalLivePP - nonBonusLivePP); totalLocalPP += playcountBonusPP; double totalDiffPP = totalLocalPP - totalLivePP; OutputDocument(new Document( new Span($"User: {userData.username}"), "\n", new Span($"Live PP: {totalLivePP:F1} (including {playcountBonusPP:F1}pp from playcount)"), "\n", new Span($"Local PP: {totalLocalPP:F1} ({totalDiffPP:+0.0;-0.0;-})"), "\n", new Grid { Columns = { GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto }, Children = { new Cell("beatmap"), new Cell("live pp"), new Cell("local pp"), new Cell("pp change"), new Cell("position change"), localOrdered.Select(item => new[] { new Cell($"{item.Beatmap.OnlineBeatmapID} - {item.Beatmap}"), new Cell($"{item.LivePP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP - item.LivePP:F1}") { Align = Align.Right }, new Cell($"{liveOrdered.IndexOf(item) - localOrdered.IndexOf(item):+0;-0;-}") { Align = Align.Center }, }) } } )); }
public override void Execute() { var displayPlays = new List <UserPlayInfo>(); var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? 0); Console.WriteLine("Getting user data..."); dynamic userData = getJsonFromApi($"get_user?k={Key}&u={ProfileName}&m={Ruleset}")[0]; Console.WriteLine("Getting user top scores..."); foreach (var play in getJsonFromApi($"get_user_best?k={Key}&u={ProfileName}&m={Ruleset}&limit=100")) { string beatmapID = play.beatmap_id; string cachePath = Path.Combine("cache", $"{beatmapID}.osu"); if (!File.Exists(cachePath)) { Console.WriteLine($"Downloading {beatmapID}.osu..."); new FileWebRequest(cachePath, $"{base_url}/osu/{beatmapID}").Perform(); } var working = new ProcessorWorkingBeatmap(cachePath, (int)play.beatmap_id); var scoreInfo = new ScoreInfo { Ruleset = ruleset.RulesetInfo, TotalScore = play.score, MaxCombo = play.maxcombo, Mods = ruleset.ConvertFromLegacyMods((LegacyMods)play.enabled_mods).ToArray(), Statistics = new Dictionary <HitResult, int>() }; scoreInfo.SetCount300((int)play.count300); scoreInfo.SetCountGeki((int)play.countgeki); scoreInfo.SetCount100((int)play.count100); scoreInfo.SetCountKatu((int)play.countkatu); scoreInfo.SetCount50((int)play.count50); scoreInfo.SetCountMiss((int)play.countmiss); var score = new ProcessorScoreDecoder(working).Parse(scoreInfo); var difficultyCalculator = ruleset.CreateDifficultyCalculator(working); var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.TrimNonDifficultyAdjustmentMods(ruleset, scoreInfo.Mods).ToArray()); var performanceCalculator = ruleset.CreatePerformanceCalculator(difficultyAttributes, score.ScoreInfo); var categories = new Dictionary <string, double>(); var localPP = performanceCalculator.Calculate(categories); var thisPlay = new UserPlayInfo { Beatmap = working.BeatmapInfo, LocalPP = localPP, LivePP = play.pp, Mods = scoreInfo.Mods.Select(m => m.Acronym).ToArray(), MissCount = play.countmiss, Accuracy = scoreInfo.Accuracy * 100, Combo = play.maxcombo, MaxCombo = (int)categories.GetValueOrDefault("Max Combo") }; displayPlays.Add(thisPlay); } var localOrdered = displayPlays.OrderByDescending(p => p.LocalPP).ToList(); var liveOrdered = displayPlays.OrderByDescending(p => p.LivePP).ToList(); int index = 0; double totalLocalPP = localOrdered.Sum(play => Math.Pow(0.95, index++) * play.LocalPP); double totalLivePP = userData.pp_raw; index = 0; double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play.LivePP); //todo: implement properly. this is pretty damn wrong. var playcountBonusPP = (totalLivePP - nonBonusLivePP); totalLocalPP += playcountBonusPP; double totalDiffPP = totalLocalPP - totalLivePP; if (OutputJson) { var json = JsonConvert.SerializeObject(new { Username = userData.username, LivePp = totalLivePP, LocalPp = totalLocalPP, PlaycountPp = playcountBonusPP, Scores = localOrdered.Select(item => new { BeatmapId = item.Beatmap.OnlineBeatmapID, BeatmapName = item.Beatmap.ToString(), item.Combo, item.Accuracy, item.MissCount, item.Mods, LivePp = item.LivePP, LocalPp = item.LocalPP, PositionChange = liveOrdered.IndexOf(item) - localOrdered.IndexOf(item) }) }); Console.Write(json); if (OutputFile != null) { File.WriteAllText(OutputFile, json); } } else { OutputDocument(new Document( new Span($"User: {userData.username}"), "\n", new Span($"Live PP: {totalLivePP:F1} (including {playcountBonusPP:F1}pp from playcount)"), "\n", new Span($"Local PP: {totalLocalPP:F1} ({totalDiffPP:+0.0;-0.0;-})"), "\n", new Grid { Columns = { GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto }, Children = { new Cell("#"), new Cell("beatmap"), new Cell("max combo"), new Cell("accuracy"), new Cell("misses"), new Cell("mods"), new Cell("live pp"), new Cell("local pp"), new Cell("pp change"), new Cell("position change"), localOrdered.Select(item => new[] { new Cell($"{localOrdered.IndexOf(item) + 1}"), new Cell($"{item.Beatmap.OnlineBeatmapID} - {item.Beatmap}"), new Cell($"{item.Combo}/{item.MaxCombo}x") { Align = Align.Right }, new Cell($"{Math.Round(item.Accuracy, 2)}%") { Align = Align.Right }, new Cell($"{item.MissCount}") { Align = Align.Right }, new Cell($"{(item.Mods.Length > 0 ? string.Join(", ", item.Mods) : "None")}") { Align = Align.Right }, new Cell($"{item.LivePP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP:F1}") { Align = Align.Right }, new Cell($"{item.LocalPP - item.LivePP:F1}") { Align = Align.Right }, new Cell($"{liveOrdered.IndexOf(item) - localOrdered.IndexOf(item):+0;-0;-}") { Align = Align.Center }, }) } }) ); } }