static void AutoBlacklist(LeaderboardData data) { for (int i = 0; i < data.allEntries.Length; i++) { PlayerEntry e = data.allEntries[i]; string reasonToAdd = null; if (e.score > 11000000) { reasonToAdd = "Score over $11 million (" + e.GetScore() + ")."; } else if (e.runend == RunEndCause.NormalClear && e.runframes < 2 * 60 * 60) { reasonToAdd = "Normal clear in under two minutes (" + e.GetTime() + ")."; } else if (e.runend == RunEndCause.NormalClear && e.runframes < 4 * 60 * 60) { reasonToAdd = "Hard clear in under four minutes (" + e.GetTime() + ")."; } else if (e.runend == RunEndCause.SpecialClear && e.runframes < 20 * 60 * 60) { reasonToAdd = "Special clear in under 20 minutes (" + e.GetTime() + ")."; } else if (e.level > 4 && e.runframes < e.level * 4 * 60) { reasonToAdd = "Less than 4 seconds per level average (" + e.GetLevel() + " in " + e.GetTime() + ")."; } if (reasonToAdd != null) { if (AddToBlacklist(e.id)) { BlacklistLog("Adding " + e.name + " (" + Convert.ToString((long)e.id, 16) + ") to blacklist: " + reasonToAdd); } } } }
public void WriteEntry(PlayerEntry entry, int rank, PageType sortedBy, string path) { //Rank stringBuilder.Append("<tr><td>"); stringBuilder.Append(Convert.ToString(rank)); //Platform stringBuilder.Append("</td><td class=\""); bool knownPlatform = true; switch (entry.platform) { case Platform.Steam: stringBuilder.Append("pc"); break; case Platform.PS4: stringBuilder.Append("ps4"); break; case Platform.Switch: stringBuilder.Append("switch"); break; default: stringBuilder.Append("unknown"); knownPlatform = false; break; } if (knownPlatform) { stringBuilder.Append("\" />"); } else { #if DEBUG stringBuilder.Append("\" >"); stringBuilder.Append(Convert.ToString((int)entry.platform, 16)); stringBuilder.Append("</td>"); #else stringBuilder.Append("\" />"); //Don't expose weirdness to end-users #endif } #if DEBUG if (true) { //DEBUG stringBuilder.Append("<td>"); stringBuilder.Append(entry.unknown1); stringBuilder.Append("</td><td>"); stringBuilder.Append(Convert.ToString(entry.unknown1, 16)); stringBuilder.Append("</td><td>"); stringBuilder.Append(entry.unknown2); stringBuilder.Append("</td><td>"); stringBuilder.Append(Convert.ToString(entry.unknown2, 16)); stringBuilder.Append("</td><td>"); stringBuilder.Append("</td>"); } #endif //Character stringBuilder.Append("<td class=\"c"); stringBuilder.Append(Convert.ToString(Convert.ToInt16(entry.character), 16).PadLeft(2, '0')); stringBuilder.Append("\"/>"); //Name string playerFile = Convert.ToString((long)entry.id, 16).PadLeft(16, '0') + ".html"; if (ongoing && !File.Exists(path + playerFile)) { playerFile = null; } stringBuilder.Append("<td>"); if (playerFile != null) { stringBuilder.Append("<a href=\""); stringBuilder.Append(playerFile); stringBuilder.Append("\">"); } stringBuilder.Append(entry.name.Replace("<", "").Replace(">", "")); if (playerFile != null) { stringBuilder.Append("</a>"); } switch (sortedBy) { case PageType.Depth: case PageType.Score: //Depth stringBuilder.Append("</td><td"); switch (entry.runend) { case RunEndCause.NormalClear: case RunEndCause.HardClear: case RunEndCause.SpecialClear: stringBuilder.Append(" class=\"clear\">"); break; default: if (entry.level >= COSMIC_OCEAN) { stringBuilder.Append(" class=\"co\">"); } else { stringBuilder.Append(">"); } break; } stringBuilder.Append(entry.GetLevel()); //Score stringBuilder.Append("</td><td class=\"score\">"); stringBuilder.Append(entry.GetScore()); break; case PageType.Time: //Time stringBuilder.Append("</td><td>"); stringBuilder.Append(entry.GetTime()); break; } //Finish row stringBuilder.AppendLine("</td></tr>"); }
public string GeneratePlayerPage(PlayerInfo pi) { //Program.Log("Generating player page for " + pi.name + " (" + pi.id + ")"); //Enable this for a lot of spam stringBuilder = new StringBuilder(); //Sort entries chronologically (oldest scores first) pi.entries.Sort((x, y) => (x.timestamp.CompareTo(y.timestamp))); //Get info ExtendedPlayerEntry latestEntry = pi.entries[pi.entries.Count - 1]; long totalFrames = 0; int normalClears = 0; int hardClears = 0; int specialClears = 0; int scVisits = 0; int coVisits = 0; bool doubleSpecial = false; List <Character> playedChars = new List <Character>(); for (int i = 0; i < pi.entries.Count; i++) { ExtendedPlayerEntry e = pi.entries[i]; totalFrames += e.runframes; switch (e.runend) { case RunEndCause.NormalClear: normalClears++; break; case RunEndCause.HardClear: hardClears++; break; case RunEndCause.SpecialClear: specialClears++; break; } if (e.level >= 19) { scVisits++; } if (e.level >= 23) { coVisits++; } //Accomplishment tracking if (i > 0 && e.runend == RunEndCause.SpecialClear) { if (pi.entries[i - 1].runend == RunEndCause.SpecialClear) { doubleSpecial = true; } } bool hasPlayedCharacter = false; for (int j = 0; j < playedChars.Count; j++) { if (playedChars[j] == pi.entries[i].character) { hasPlayedCharacter = true; } } if (!hasPlayedCharacter) { playedChars.Add(pi.entries[i].character); } } //Check for accomplishments List <Accomplishment> accomplishments = new List <Accomplishment>(); if (specialClears >= 10) { accomplishments.Add(new Accomplishment("Legend", "This player has reached 7-99 ten times!", "legend.png")); } if (pi.bestLevelRank == 1) { accomplishments.Add(new Accomplishment("#1 Depth", "This player has achieved rank #1 in Depth on a daily!", "1depth.png")); } if (pi.bestScoreRank == 1) { accomplishments.Add(new Accomplishment("#1 Score", "This player has achieved rank #1 in Score on a daily!", "1score.png")); } if (pi.bestHardTimeRank == 1) { accomplishments.Add(new Accomplishment("#1 Hard", "This player got first place in a Hard speedrun on a daily!", "1hard.png")); } if (pi.bestNormalTimeRank == 1) { accomplishments.Add(new Accomplishment("#1 Normal", "This player got first place in a Normal speedrun on a daily!", "1normal.png")); } if (pi.bestSpecialTimeRank == 1) { accomplishments.Add(new Accomplishment("#1 Special", "This player was the fastest to get a Special clear on a daily!", "1special.png")); } if (doubleSpecial) { accomplishments.Add(new Accomplishment("Consistent", "This player has reached 7-99 twice in a row!", "consistent.png")); } if (pi.bestSpecialTime != -1 && pi.bestSpecialTime < 116 * 60 * 60) { accomplishments.Add(new Accomplishment("Celeritas", "This player reached 7-99 within 1 hour and 56 minutes!", "celeritas.png")); } if (specialClears > 0) { accomplishments.Add(new Accomplishment("One with the Cosmos", "This player has reached 7-99!", "799.png")); } if (pi.bestScore > 2000000) { accomplishments.Add(new Accomplishment("Treasure Hunter", "This player has achieved a top score of over $2,000,000!", "2million.png")); } if (pi.bestHardTime != -1 && pi.bestHardTime < 20 * 60 * 60) { accomplishments.Add(new Accomplishment("brb killing primordial chaos", "This player has completed a Hard run in under 20 minutes!", "fasthard.png")); } if ((pi.bestNormalTime != -1 && pi.bestNormalTime < 8 * 60 * 60) || (pi.bestHardTime != -1 && pi.bestHardTime < 8 * 60 * 60)) { accomplishments.Add(new Accomplishment("Really Fast", "This player has completed a run in under 8 minutes!", "fastnormal.png")); } if (pi.topScoresAverage > 1000000) { accomplishments.Add(new Accomplishment("More like 'Palace of Treasure' am I right", "This player has a top 10 average score of over $1,000,000!", "million.png")); } if (coVisits >= 10) { accomplishments.Add(new Accomplishment("Astronaut", "This player has reached the Cosmic Ocean ten times!", "astronaut.png")); } if (pi.entries.Count >= 365) { accomplishments.Add(new Accomplishment("Yearly Challenge", "This player has participated 365 times!", "year.png")); } if (playedChars.Count == 20) { accomplishments.Add(new Accomplishment("I Main Random", "This player has played with every character!", "allchars.png")); } //Write header WriteHeader(PageType.Player, pi.name); //Return link stringBuilder.AppendLine("<center><a href=\"depth.html\">[Click here to return to leaderboard listings]</a><br /><br />"); //Name(s) stringBuilder.Append("<font size=\"7\">"); stringBuilder.Append(pi.name); stringBuilder.Append("</font>"); BR(); //Filter duplicate alternate names for (int i = 0; i < pi.previousNames.Count; i++) { if (pi.previousNames[i] == pi.name) { pi.previousNames.RemoveAt(i); i--; continue; } for (int j = 0; j < pi.previousNames.Count; j++) { if (i == j) { continue; } if (pi.previousNames[i] == pi.previousNames[j]) { pi.previousNames.RemoveAt(j); j--; } } } //List alternate names for (int i = 0; i < pi.previousNames.Count; i++) { stringBuilder.Append("aka "); stringBuilder.Append(pi.previousNames[i]); BR(); } //Last used character stringBuilder.Append("<table><tr><td class=\"c"); stringBuilder.Append(Convert.ToString((int)latestEntry.character, 16).PadLeft(2, '0')); stringBuilder.Append("\"/></tr></table>"); BR(); //Info PlayerAddition addition = null; if (Program.additions.ContainsKey(pi.id)) { addition = Program.additions[pi.id]; } if (addition != null) { stringBuilder.Append(addition.html); BR(); } if (latestEntry.platform == Platform.Steam) { stringBuilder.Append("<a href=\"https://steamcommunity.com/profiles/"); stringBuilder.Append(pi.id); stringBuilder.AppendLine("\">Visit Steam profile</a><br />"); } BR(); //Accomplishments if (accomplishments.Count > 0) { stringBuilder.AppendLine("<table>"); for (int i = 0; i < accomplishments.Count; i++) { WriteKeyValue("<img src=\"" + accomplishments[i].icon + "\">", "<b>" + accomplishments[i].name + "</b>: " + accomplishments[i].description); } stringBuilder.AppendLine("</table><br />"); } //From here on, do not show links until hovered stringBuilder.AppendLine("<div class=\"hidelink\">"); //General stats stringBuilder.AppendLine("<font size=\"5\">General statistics</font><br />"); stringBuilder.AppendLine("<table>"); WriteKeyValue("Runs", pi.entries.Count); WriteKeyValue("Total playtime", PlayerEntry.GetTime(totalFrames)); WriteKeyValue("Normal clears", normalClears); WriteKeyValue("Sunken City visits", scVisits); WriteKeyValue("Hard clears", hardClears); WriteKeyValue("CO visits", coVisits); WriteKeyValue("Special clears", specialClears); stringBuilder.AppendLine("</table><br /><br />"); //Top average scores stringBuilder.AppendLine("<font size=\"5\">Top averages</font><br />"); stringBuilder.AppendLine("<table>"); int wholePart = (int)Math.Floor(pi.topLevelsAverage); float fraction = pi.topLevelsAverage - wholePart; stringBuilder.Append("<tr><td><b>Top 10 average depth</b></td><td>"); stringBuilder.Append(PlayerEntry.GetLevel(wholePart)); stringBuilder.Append("<font size=\"1\">."); string fractionalString = Convert.ToString(Math.Round(fraction, 3)); if (fractionalString.Length < 2) { fractionalString = "000"; } else { fractionalString = fractionalString.Substring(2).PadRight(3, '0'); } stringBuilder.Append(fractionalString); stringBuilder.Append("</font>"); stringBuilder.AppendLine("</td></tr>"); WriteKeyValue("Top 10 average score", PlayerEntry.GetScore(pi.topScoresAverage)); WriteKeyValue("Top 5 average time", PlayerEntry.GetTime(pi.topTimesAverage)); stringBuilder.AppendLine("</table><br /><br />"); //Best rankings stringBuilder.AppendLine("<font size=\"5\">Best rankings</font><br />"); stringBuilder.AppendLine("<table>"); WriteTimestampedValue("Best depth ranking", pi.bestLevelRank, pi.bestLevelRankDates); WriteTimestampedValue("Best score ranking", pi.bestScoreRank, pi.bestScoreRankDates); if (pi.bestNormalTimeRankDates.Count == 0) { WriteKeyValue("Best Normal ranking", "<i>N/A</i>"); } else { WriteTimestampedValue("Best Normal ranking", pi.bestNormalTimeRank, pi.bestNormalTimeRankDates); } if (pi.bestHardTimeRankDates.Count == 0) { WriteKeyValue("Best Hard ranking", "<i>N/A</i>"); } else { WriteTimestampedValue("Best Hard ranking", pi.bestHardTimeRank, pi.bestHardTimeRankDates); } if (pi.bestSpecialTimeRankDates.Count == 0) { WriteKeyValue("Best Special ranking", "<i>N/A</i>"); } else { WriteTimestampedValue("Best Special ranking", pi.bestSpecialTimeRank, pi.bestSpecialTimeRankDates); } stringBuilder.AppendLine("</table><br /><br />"); //Top results stringBuilder.AppendLine("<font size=\"5\">Top results</font><br />"); stringBuilder.AppendLine("<table>"); WriteTimestampedValue("Best depth", PlayerEntry.GetLevel(pi.bestLevel), pi.bestLevelDates); WriteTimestampedValue("Best score", PlayerEntry.GetScore(pi.bestScore), pi.bestScoreDate); if (pi.bestNormalTime == -1) { WriteKeyValue("Fastest Normal clear", "<i>N/A</i>"); } else { WriteTimestampedValue("Fastest Normal clear", PlayerEntry.GetTime(pi.bestNormalTime), pi.bestNormalTimeDate); } if (pi.bestHardTime == -1) { WriteKeyValue("Fastest Hard clear", "<i>N/A</i>"); } else { WriteTimestampedValue("Fastest Hard clear", PlayerEntry.GetTime(pi.bestHardTime), pi.bestHardTimeDate); } if (pi.bestSpecialTime == -1) { WriteKeyValue("Fastest Special clear", "<i>N/A</i>"); } else { WriteTimestampedValue("Fastest Special clear", PlayerEntry.GetTime(pi.bestSpecialTime), pi.bestSpecialTimeDate); } stringBuilder.AppendLine("</table><br /><br />"); //All results pi.entries.Sort((x, y) => (y.timestamp.CompareTo(x.timestamp))); //Sort in reverse chronological order for display stringBuilder.AppendLine("<font size=\"5\">All results</font><br />"); stringBuilder.AppendLine("<table style=\"min-width: 0.5em;\">"); for (int i = 0; i < pi.entries.Count; i++) { ExtendedPlayerEntry epe = pi.entries[i]; bool clear = (epe.runend == RunEndCause.NormalClear || epe.runend == RunEndCause.HardClear || epe.runend == RunEndCause.SpecialClear); stringBuilder.Append("<tr><td>"); string tsString = Program.IntTimestampToString(epe.timestamp); stringBuilder.Append(" <a href=\""); stringBuilder.Append(tsString); stringBuilder.Append("_depth.html\">"); stringBuilder.Append(tsString); stringBuilder.Append("</a> "); stringBuilder.Append("</td><td /><td class=\"c"); stringBuilder.Append(Convert.ToString((int)epe.character, 16).PadLeft(2, '0')); stringBuilder.Append("\" /><td /><td>#"); stringBuilder.Append(epe.levelRank); stringBuilder.Append("</td><td"); if (clear) { stringBuilder.Append(" class=\"clear\">"); } else if (pi.entries[i].level >= COSMIC_OCEAN) { stringBuilder.Append(" class=\"co\">"); } else { stringBuilder.Append(">"); } stringBuilder.Append(epe.GetLevel()); stringBuilder.Append("</td><td /><td>#"); stringBuilder.Append(epe.scoreRank); stringBuilder.Append("</td><td>$"); stringBuilder.Append(epe.GetScore()); if (clear) { stringBuilder.Append("</td><td /><td>#"); switch (epe.runend) { case RunEndCause.NormalClear: stringBuilder.Append(epe.normalTimeRank); break; case RunEndCause.HardClear: stringBuilder.Append(epe.hardTimeRank); break; case RunEndCause.SpecialClear: stringBuilder.Append(epe.specialTimeRank); break; } stringBuilder.Append("</td><td>"); stringBuilder.Append(epe.GetTime()); } stringBuilder.AppendLine("</td></tr>"); } stringBuilder.AppendLine("</table><br />"); //Finish stringBuilder.AppendLine("</div>"); //hidelink end WriteFooter(PageType.Player, true, Convert.ToString((long)pi.id, 16).PadLeft(16, '0') + ".json"); //Write to file return(stringBuilder.ToString()); }
public string GenerateStats(string description) { Program.Log("Generating " + description); stringBuilder = new StringBuilder(); WriteHeader(PageType.Stats); //--BODY-- stringBuilder.AppendLine("<body><center>"); stringBuilder.Append("<font size=\"7\">Stats for "); stringBuilder.Append(dateTime.Day); stringBuilder.Append(" "); stringBuilder.Append(dateTime.ToString("MMMM", System.Globalization.CultureInfo.CreateSpecificCulture("en"))); stringBuilder.Append(" "); stringBuilder.Append(dateTime.Year); stringBuilder.AppendLine("</font><br />"); if (ongoing) { stringBuilder.AppendLine("(Ongoing)<br />"); } WriteDateNavigation("stats.html"); stringBuilder.Append("<br /><br />"); if (ongoing) { stringBuilder.Append("<div id=\"count\" >Loading...</div><br /><br />"); } WriteTypeNavigation(PageType.Stats); stringBuilder.AppendLine("<br /><br />"); //Collect stats Dictionary <int, int> deathsPerLevel = new Dictionary <int, int>(); Dictionary <int, int> survivorsPerLevel = new Dictionary <int, int>(); Dictionary <Character, List <PlayerEntry> > characterEntries = new Dictionary <Character, List <PlayerEntry> >(); for (int i = 0; i < entries.Length; i++) { PlayerEntry entry = entries[i]; //Level for (int iLevel = entry.level; iLevel > 0; iLevel--) { bool cleared = (entry.runend == RunEndCause.NormalClear || entry.runend == RunEndCause.HardClear || entry.runend == RunEndCause.SpecialClear); if (!deathsPerLevel.ContainsKey(iLevel)) { deathsPerLevel.Add(iLevel, 0); } if (!survivorsPerLevel.ContainsKey(iLevel)) { survivorsPerLevel.Add(iLevel, 0); } if (iLevel == entry.level && !cleared) { deathsPerLevel[iLevel]++; continue; } survivorsPerLevel[iLevel]++; } //Characters if (!characterEntries.ContainsKey(entry.character)) { characterEntries.Add(entry.character, new List <PlayerEntry>()); } characterEntries[entry.character].Add(entry); } //Death stats stringBuilder.AppendLine("<font size=\"6\">Level deadliness</font><br />"); if (!ongoing) { stringBuilder.Append("(You can also check out <a href=\"https://jhay.net/spelunky2daily/?day="); stringBuilder.Append(Program.GetYYYYMMDD(dateTime)); stringBuilder.AppendLine("\">JeremyHay's site</a> for more survival stats.)<br />"); } stringBuilder.AppendLine("<br />"); stringBuilder.AppendLine("<table border=\"1\">"); stringBuilder.AppendLine("<tr class=\"top\"><td></td><td>Level</td><td>Survivors</td><td>Deaths</td><td>Death rate</td></tr>"); for (int i = 1; i < MAX_LEVEL; i++) { int survivors = 0; int deaths = 0; if (survivorsPerLevel.ContainsKey(i)) { survivors = survivorsPerLevel[i]; } if (deathsPerLevel.ContainsKey(i)) { deaths = deathsPerLevel[i]; } if (survivors + deaths > 0 || i < COSMIC_OCEAN) { float deathRate = 0; if (survivors + deaths > 0) { deathRate = (float)deaths / (float)(survivors + deaths); } stringBuilder.Append("<tr"); if (deathRate >= 0.5f) { stringBuilder.Append(" class=\"deadly\""); } else if (deathRate >= 0.3f) { stringBuilder.Append(" class=\"danger\""); } stringBuilder.Append("><td class=\"w"); string levelName = PlayerEntry.GetLevel(i); stringBuilder.Append(levelName.Substring(0, 1)); if (i >= COSMIC_OCEAN) { stringBuilder.Append("5"); } stringBuilder.Append("\" /><td>"); stringBuilder.Append(levelName); stringBuilder.Append("</td><td>"); stringBuilder.Append(survivors); stringBuilder.Append("</td><td>"); stringBuilder.Append(deaths); stringBuilder.Append("</td><td>"); stringBuilder.Append(Convert.ToString(Math.Round(deathRate * 100f, 1)) + "%"); stringBuilder.AppendLine("</td></tr>"); } } stringBuilder.AppendLine("</table><br/><br/>"); //Characters List <KeyValuePair <Character, List <PlayerEntry> > > characterList = characterEntries.ToList(); //Sorry :( Dictionary <Character, string> characterPlayerNames = new Dictionary <Character, string>(); //Character usage stringBuilder.AppendLine("<font size=\"6\">Character usage</font><br /><br />"); stringBuilder.AppendLine("<table border=\"1\">"); characterList.Sort((x, y) => y.Value.Count.CompareTo(x.Value.Count)); for (int i = 0; i < characterList.Count; i++) { stringBuilder.Append("<tr><td>"); stringBuilder.Append(i + 1); stringBuilder.Append("</td><td class=\"c"); stringBuilder.Append(Convert.ToString((int)characterList[i].Key, 16).PadLeft(2, '0')); stringBuilder.Append("\" /><td>"); stringBuilder.Append(characterList[i].Value.Count); stringBuilder.Append("</td><td>"); stringBuilder.Append(Math.Round(((float)characterList[i].Value.Count / (float)entries.Length) * 100f, 1)); stringBuilder.AppendLine("%</td></tr>"); } stringBuilder.AppendLine("</table><br/><br/>"); //Character best depth List <KeyValuePair <Character, int> > characterDepths = new List <KeyValuePair <Character, int> >(); characterPlayerNames.Clear(); for (int i = 0; i < characterList.Count; i++) { int bestDepth = 1; string bestPlayer = "Unknown"; for (int j = 0; j < characterList[i].Value.Count; j++) { PlayerEntry entry = characterList[i].Value[j]; if (entry.level > bestDepth) { bestDepth = entry.level; bestPlayer = entry.name; } } characterDepths.Add(new KeyValuePair <Character, int>(characterList[i].Key, bestDepth)); characterPlayerNames.Add(characterList[i].Key, bestPlayer); } characterDepths.Sort((x, y) => y.Value.CompareTo(x.Value)); stringBuilder.AppendLine("<font size=\"6\">Character best depth</font><br /><br />"); stringBuilder.AppendLine("<table border=\"1\">"); for (int i = 0; i < characterList.Count; i++) { stringBuilder.Append("<tr><td>"); stringBuilder.Append(i + 1); stringBuilder.Append("</td><td class=\"c"); stringBuilder.Append(Convert.ToString((int)characterDepths[i].Key, 16).PadLeft(2, '0')); stringBuilder.Append("\" /><td>"); stringBuilder.Append(characterPlayerNames[characterDepths[i].Key]); stringBuilder.Append("</td><td>"); stringBuilder.Append(PlayerEntry.GetLevel(characterDepths[i].Value)); stringBuilder.AppendLine("</td></tr>"); } stringBuilder.AppendLine("</table><br/><br/>"); //Character best score List <KeyValuePair <Character, int> > characterScores = new List <KeyValuePair <Character, int> >(); characterPlayerNames.Clear(); for (int i = 0; i < characterList.Count; i++) { int bestScore = 0; string bestPlayer = "Unknown"; for (int j = 0; j < characterList[i].Value.Count; j++) { PlayerEntry entry = characterList[i].Value[j]; if (entry.score > bestScore) { bestScore = entry.score; bestPlayer = entry.name; } } characterScores.Add(new KeyValuePair <Character, int>(characterList[i].Key, bestScore)); characterPlayerNames.Add(characterList[i].Key, bestPlayer); } characterScores.Sort((x, y) => y.Value.CompareTo(x.Value)); stringBuilder.AppendLine("<font size=\"6\">Character best score</font><br /><br />"); stringBuilder.AppendLine("<table border=\"1\">"); for (int i = 0; i < characterList.Count; i++) { stringBuilder.Append("<tr><td>"); stringBuilder.Append(i + 1); stringBuilder.Append("</td><td class=\"c"); stringBuilder.Append(Convert.ToString((int)characterScores[i].Key, 16).PadLeft(2, '0')); stringBuilder.Append("\" /><td>"); stringBuilder.Append(characterPlayerNames[characterScores[i].Key]); stringBuilder.Append("</td><td>"); stringBuilder.Append(PlayerEntry.GetScore(characterScores[i].Value)); stringBuilder.AppendLine("</td></tr>"); } stringBuilder.AppendLine("</table><br/><br/>"); //Character best time List <KeyValuePair <Character, int> > characterTimes = new List <KeyValuePair <Character, int> >(); characterPlayerNames.Clear(); int maxTime = 60 /*frames*/ * (((9 /*hours*/ * 60) + 59 /*minutes*/) * 60 + 59 /*seconds*/); for (int i = 0; i < characterList.Count; i++) { int bestTime = maxTime; string bestPlayer = "Unknown"; for (int j = 0; j < characterList[i].Value.Count; j++) { PlayerEntry entry = characterList[i].Value[j]; switch (entry.runend) { case RunEndCause.NormalClear: case RunEndCause.HardClear: case RunEndCause.SpecialClear: break; default: continue; } if (entry.runframes < bestTime) { bestTime = entry.runframes; bestPlayer = entry.name; } } characterTimes.Add(new KeyValuePair <Character, int>(characterList[i].Key, bestTime)); characterPlayerNames.Add(characterList[i].Key, bestPlayer); } characterTimes.Sort((x, y) => x.Value.CompareTo(y.Value)); stringBuilder.AppendLine("<font size=\"6\">Character best time</font><br /><br />"); stringBuilder.AppendLine("<table border=\"1\">"); for (int i = 0; i < characterList.Count; i++) { bool finished = false; if (characterTimes[i].Value != maxTime) { finished = true; } stringBuilder.Append("<tr><td>"); stringBuilder.Append(i + 1); stringBuilder.Append("</td><td class=\"c"); stringBuilder.Append(Convert.ToString((int)characterTimes[i].Key, 16).PadLeft(2, '0')); stringBuilder.Append("\" /><td>"); if (finished) { stringBuilder.Append(characterPlayerNames[characterTimes[i].Key]); } else { stringBuilder.Append("<i>N/A</i>"); } stringBuilder.Append("</td><td>"); if (finished) { stringBuilder.Append(PlayerEntry.GetTime(characterTimes[i].Value)); } else { stringBuilder.Append("<i>did not finish</i>"); } stringBuilder.AppendLine("</td></tr>"); } stringBuilder.AppendLine("</table><br/><br/>"); //Finish WriteFooter(PageType.Stats, true); //Write to file return(stringBuilder.ToString()); }