From de9dfaf36256151b4b4ecc6e5ebe69c5364fba7d Mon Sep 17 00:00:00 2001 From: SilicaAndPina Date: Sun, 27 Dec 2020 15:42:01 +1300 Subject: [PATCH] Add highscores and best times. --- DataCollection/gamedata.json | 9 ++ .../Horse Isle Server/Game/Messages.cs | 35 +++++++ .../Horse Isle Server/Game/Meta.cs | 51 +++++++++- .../Horse Isle Server/Player/Highscore.cs | 87 +++++++++++++++-- .../Horse Isle Server/Player/User.cs | 3 +- .../Horse Isle Server/Server/Database.cs | 97 +++++++++++++++---- .../Horse Isle Server/Server/GameDataJson.cs | 11 +++ .../Horse Isle Server/Server/GameServer.cs | 27 +++++- .../Horse Isle Server/Server/PacketBuilder.cs | 8 +- 9 files changed, 291 insertions(+), 37 deletions(-) diff --git a/DataCollection/gamedata.json b/DataCollection/gamedata.json index dee5886..89a22a9 100644 --- a/DataCollection/gamedata.json +++ b/DataCollection/gamedata.json @@ -91,6 +91,15 @@ "end_of_meta":"^Z", "back_to_map":"^M", "long_full_line":"^L", + "highscores":{ + "header_meta":"^ATMini-Game Rankings^H", + "highscore_format":"%GAMETITLE% Rank: #%RANKING% With: %SCORE% points (%TOTALPLAYS% plays)
", + "besttime_format":"%GAMETITLE% Rank: #%RANKING% With: %SCORE% time score (%TOTALPLAYS% plays)
", + "game_highscore_header":"Top 20 High Scores for %GAMETITLE%
", + "game_highscore_format":"#%RANKING%: %SCORE% points -- %USERNAME% [played %TOTALPLAYS% times]
", + "game_besttime_header":"Top 20 Best Times for %GAMETITLE%
", + "game_besttime_format":"#%RANKING% Best Time: %SCORE% sec -- %USERNAME% [played %TOTALPLAYS% times]
" + }, "quest_log":{ "header_meta":"^ATYour Horse Isle Adventure Log^H", "quest_format":"%TITLE% (%QUESTPOINTS%qp) [%DIFFICULTY%] %COMPLETION%
", diff --git a/Horse Isle Server/Horse Isle Server/Game/Messages.cs b/Horse Isle Server/Horse Isle Server/Game/Messages.cs index e2c8bb0..045b5da 100644 --- a/Horse Isle Server/Horse Isle Server/Game/Messages.cs +++ b/Horse Isle Server/Horse Isle Server/Game/Messages.cs @@ -163,6 +163,17 @@ namespace HISP.Game public static string SellButton; public static string SellAllButton; + // Highscore List + public static string HighscoreHeaderMeta; + public static string HighscoreFormat; + public static string BestTimeFormat; + + public static string GameBestTimeFormat; + public static string GameBestTimeHeaderFormat; + public static string GameHighScoreHeaderFormat; + public static string GameHighScoreFormat; + + // Shop public static string ThingsIAmSelling; public static string ThingsYouSellMe; @@ -229,6 +240,30 @@ namespace HISP.Game // Click public static string NothingInterestingHere; + public static string FormatBestTimeHeader(string gameName) + { + return GameBestTimeHeaderFormat.Replace("%GAMETITLE%", gameName); + } + public static string FormatBestTimeListEntry(int ranking, int score, string username, int totalplays) + { + return GameBestTimeFormat.Replace("%RANKING%", ranking.ToString("N0")).Replace("%SCORE%", score.ToString().Insert(score.ToString().Length - 2, ".")).Replace("%USERNAME%", username).Replace("%TOTALPLAYS%", totalplays.ToString("N0")); + } + public static string FormatHighscoreHeader(string gameName) + { + return GameHighScoreHeaderFormat.Replace("%GAMETITLE%", gameName); + } + public static string FormatHighscoreListEntry(int ranking, int score, string username, int totalplays) + { + return GameHighScoreFormat.Replace("%RANKING%", ranking.ToString("N0")).Replace("%SCORE%", score.ToString("N0")).Replace("%USERNAME%", username).Replace("%TOTALPLAYS%", totalplays.ToString("N0")); + } + public static string FormatHighscoreStat(string gameTitle, int ranking, int score, int totalplays) + { + return HighscoreFormat.Replace("%GAMETITLE%", gameTitle).Replace("%RANKING%", ranking.ToString("N0")).Replace("%SCORE%", score.ToString("N0")).Replace("%TOTALPLAYS%", totalplays.ToString("N0")); + } + public static string FormatBestTimeStat(string gameTitle, int ranking, int score, int totalplays) + { + return BestTimeFormat.Replace("%GAMETITLE%", gameTitle).Replace("%RANKING%", ranking.ToString("N0")).Replace("%SCORE%", score.ToString()).Replace("%TOTALPLAYS%", totalplays.ToString("N0")); + } public static string FormatMoneyEarnedMessage(int money) { return YouEarnedMoneyFormat.Replace("%MONEY%", money.ToString("N0")); diff --git a/Horse Isle Server/Horse Isle Server/Game/Meta.cs b/Horse Isle Server/Horse Isle Server/Game/Meta.cs index dae229a..e0e6ea4 100644 --- a/Horse Isle Server/Horse Isle Server/Game/Meta.cs +++ b/Horse Isle Server/Horse Isle Server/Game/Meta.cs @@ -225,7 +225,6 @@ namespace HISP.Game return message; } - public static string SelectPlayerStatFormat(int statValue) { int curValue = 1000; @@ -240,7 +239,55 @@ namespace HISP.Game } throw new Exception("A mathematically impossible error occured. please check wether the laws of physics still apply."); } - + + public static string BuildTopHighscores(string gameName) + { + Highscore.HighscoreTableEntry[] scores = Database.GetTopScores(gameName, 20); + if (scores.Length <= 0) + return "No scores recorded."; + string message = ""; + + message += Messages.FormatHighscoreHeader(gameName); + + for (int i = 0; i < scores.Length; i++) + { + message += Messages.FormatHighscoreListEntry(i+1, scores[i].Score, Database.GetUsername(scores[i].UserId), scores[i].TimesPlayed); + } + message += Messages.BackToMap; + message += Messages.MetaTerminator; + return message; + } + public static string BuildTopTimes(string gameName) + { + Highscore.HighscoreTableEntry[] scores = Database.GetTopScores(gameName, 20); + if (scores.Length <= 0) + return "No times recorded."; + string message = ""; + + message += Messages.FormatBestTimeHeader(gameName); + + for (int i = 0; i < scores.Length; i++) + { + message += Messages.FormatBestTimeListEntry(i+1, scores[i].Score, Database.GetUsername(scores[i].UserId), scores[i].TimesPlayed); + } + message += Messages.BackToMap; + message += Messages.MetaTerminator; + return message; + } + public static string BuildMinigameRankingsForUser(User user) + { + string message = Messages.HighscoreHeaderMeta; + foreach(Highscore.HighscoreTableEntry highscore in user.Highscores.HighscoreList) + { + if (highscore.Type == "SCORE") + message += Messages.FormatHighscoreStat(highscore.GameName, Database.GetRanking(highscore.Score, highscore.GameName), highscore.Score, highscore.TimesPlayed); + else if(highscore.Type == "TIME") + message += Messages.FormatBestTimeStat(highscore.GameName, Database.GetRanking(highscore.Score, highscore.GameName), highscore.Score, highscore.TimesPlayed); + } + message += Messages.BackToMap; + message += Messages.MetaTerminator; + return message; + } public static string BuildPrivateNotes(User user) { string message = ""; diff --git a/Horse Isle Server/Horse Isle Server/Player/Highscore.cs b/Horse Isle Server/Horse Isle Server/Player/Highscore.cs index 50caf9b..eab2998 100644 --- a/Horse Isle Server/Horse Isle Server/Player/Highscore.cs +++ b/Horse Isle Server/Horse Isle Server/Player/Highscore.cs @@ -1,31 +1,106 @@ using HISP.Server; - +using System.Collections.Generic; namespace HISP.Player { class Highscore { - public static bool RegisterHighscore(int playerId, string gameTitle, int score, bool time) + public class HighscoreTableEntry + { + public int UserId; + public string GameName; + public int Wins; + public int Looses; + public int TimesPlayed; + public int Score; + public string Type; + } + public HighscoreTableEntry[] HighscoreList + { + get + { + return highScoreList.ToArray(); + } + } + + private User baseUser; + private List highScoreList = new List(); + + public Highscore(User user) + { + baseUser = user; + HighscoreTableEntry[] highscores = Database.GetPlayerHighScores(user.Id); + foreach (HighscoreTableEntry highscore in highscores) + highScoreList.Add(highscore); + } + public HighscoreTableEntry GetHighscore(string gameTitle) + { + foreach (HighscoreTableEntry highscore in HighscoreList) + { + if (highscore.GameName == gameTitle) + { + return highscore; + } + } + throw new KeyNotFoundException("Highscore for " + gameTitle + " Not found."); + } + public bool HasHighscore(string gameTitle) + { + foreach(HighscoreTableEntry highscore in HighscoreList) + { + if(highscore.GameName == gameTitle) + { + return true; + } + } + return false; + } + + public bool UpdateHighscore(string gameTitle, int score, bool time) { bool isNewScore = true; - if (!Database.PlayerHasHighscore(playerId, gameTitle)) + if (!HasHighscore(gameTitle)) { - Database.AddNewHighscore(playerId, gameTitle, score, time ? "TIME" : "SCORE"); + string type = time ? "TIME" : "SCORE"; + Database.AddNewHighscore(baseUser.Id, gameTitle, score, type); + + HighscoreTableEntry newHighscore = new HighscoreTableEntry(); + newHighscore.UserId = baseUser.Id; + newHighscore.GameName = gameTitle; + newHighscore.Wins = 0; + newHighscore.Looses = 0; + newHighscore.TimesPlayed = 1; + newHighscore.Score = score; + newHighscore.Type = type; + highScoreList.Add(newHighscore); + return isNewScore; } else { - int currentScore = Database.GetHighscore(playerId, gameTitle); + int currentScore = GetHighscore(gameTitle).Score; if (score < currentScore) { score = currentScore; isNewScore = false; } - Database.UpdateHighscore(playerId, gameTitle, score); + Database.UpdateHighscore(baseUser.Id, gameTitle, score); + + for(int i = 0; i < highScoreList.Count; i++) + { + + if(highScoreList[i].GameName == gameTitle) + { + highScoreList[i].TimesPlayed += 1; + highScoreList[i].Score = score; + } + } + return isNewScore; } + } } } diff --git a/Horse Isle Server/Horse Isle Server/Player/User.cs b/Horse Isle Server/Horse Isle Server/Player/User.cs index d15b4d3..0c0fce8 100644 --- a/Horse Isle Server/Horse Isle Server/Player/User.cs +++ b/Horse Isle Server/Horse Isle Server/Player/User.cs @@ -39,6 +39,7 @@ namespace HISP.Player public Npc.NpcEntry LastTalkedToNpc; public Shop LastShoppedAt; public PlayerQuests Quests; + public Highscore Highscores; public int FreeMinutes { get @@ -372,7 +373,7 @@ namespace HISP.Player Gender = Database.GetGender(UserId); MailBox = new Mailbox(this); - + Highscores = new Highscore(this); // Generate SecCodes diff --git a/Horse Isle Server/Horse Isle Server/Server/Database.cs b/Horse Isle Server/Horse Isle Server/Server/Database.cs index 296014d..228f45d 100644 --- a/Horse Isle Server/Horse Isle Server/Server/Database.cs +++ b/Horse Isle Server/Horse Isle Server/Server/Database.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using MySqlConnector; using HISP.Game; +using HISP.Player; namespace HISP.Server { @@ -1688,22 +1689,6 @@ namespace HISP.Server } } - public static bool PlayerHasHighscore(int playerId, string gameTitle) - { - using (MySqlConnection db = new MySqlConnection(ConnectionString)) - { - db.Open(); - MySqlCommand sqlCommand = db.CreateCommand(); - sqlCommand.CommandText = "SELECT COUNT(1) FROM Leaderboards WHERE playerId=@playerId AND minigame=@gameTitle"; - sqlCommand.Parameters.AddWithValue("@playerId", playerId); - sqlCommand.Parameters.AddWithValue("@gameTitle", gameTitle); - sqlCommand.Prepare(); - int count = Convert.ToInt32(sqlCommand.ExecuteScalar()); - - sqlCommand.Dispose(); - return count >= 1; - } - } public static void AddNewHighscore(int playerId, string gameTitle, int score, string type) { using (MySqlConnection db = new MySqlConnection(ConnectionString)) @@ -1724,20 +1709,90 @@ namespace HISP.Server } } - public static int GetHighscore(int playerId, string gameTitle) + public static Highscore.HighscoreTableEntry[] GetPlayerHighScores(int playerId) { + List entires = new List(); using (MySqlConnection db = new MySqlConnection(ConnectionString)) { db.Open(); MySqlCommand sqlCommand = db.CreateCommand(); - sqlCommand.CommandText = "SELECT score FROM Leaderboards WHERE playerId=@playerId AND minigame=@gameTitle"; + sqlCommand.CommandText = "SELECT * FROM leaderboards WHERE playerId=@playerId ORDER BY score ASC"; sqlCommand.Parameters.AddWithValue("@playerId", playerId); - sqlCommand.Parameters.AddWithValue("@gameTitle", gameTitle); sqlCommand.Prepare(); - int score = Convert.ToInt32(sqlCommand.ExecuteScalar()); + MySqlDataReader reader = sqlCommand.ExecuteReader(); + + while (reader.Read()) + { + Highscore.HighscoreTableEntry highscoreEntry = new Highscore.HighscoreTableEntry(); + highscoreEntry.UserId = reader.GetInt32(0); + highscoreEntry.GameName = reader.GetString(1); + highscoreEntry.Wins = reader.GetInt32(2); + highscoreEntry.Looses = reader.GetInt32(3); + highscoreEntry.TimesPlayed = reader.GetInt32(4); + highscoreEntry.Score = reader.GetInt32(5); + highscoreEntry.Type = reader.GetString(6); + entires.Add(highscoreEntry); + } + sqlCommand.Dispose(); - return score; + return entires.ToArray(); + } + } + + public static Highscore.HighscoreTableEntry[] GetTopScores(string gameTitle, int limit) + { + List entires = new List(); + using (MySqlConnection db = new MySqlConnection(ConnectionString)) + { + db.Open(); + MySqlCommand sqlCommand = db.CreateCommand(); + sqlCommand.CommandText = "SELECT * FROM leaderboards WHERE minigame=@gameTitle ORDER BY score ASC LIMIT @limit"; + sqlCommand.Parameters.AddWithValue("@gameTitle", gameTitle); + sqlCommand.Parameters.AddWithValue("@limit", limit); + sqlCommand.Prepare(); + MySqlDataReader reader = sqlCommand.ExecuteReader(); + + while(reader.Read()) + { + Highscore.HighscoreTableEntry highscoreEntry = new Highscore.HighscoreTableEntry(); + highscoreEntry.UserId = reader.GetInt32(0); + highscoreEntry.GameName = gameTitle; + highscoreEntry.Wins = reader.GetInt32(2); + highscoreEntry.Looses = reader.GetInt32(3); + highscoreEntry.TimesPlayed = reader.GetInt32(4); + highscoreEntry.Score = reader.GetInt32(5); + highscoreEntry.Type = reader.GetString(6); + entires.Add(highscoreEntry); + } + + + sqlCommand.Dispose(); + return entires.ToArray(); + } + } + + public static int GetRanking(int score, string gameTitle) + { + using (MySqlConnection db = new MySqlConnection(ConnectionString)) + { + + db.Open(); + MySqlCommand sqlCommand = db.CreateCommand(); + sqlCommand.CommandText = "SELECT DISTINCT score FROM leaderboards WHERE minigame=@gameTitle ORDER BY score ASC"; + sqlCommand.Parameters.AddWithValue("@gameTitle", gameTitle); + sqlCommand.Prepare(); + MySqlDataReader reader = sqlCommand.ExecuteReader(); + int i = 1; + while(reader.Read()) + { + if (reader.GetInt32(0) == score) + break; + i++; + } + + sqlCommand.Dispose(); + return i; } } public static void UpdateHighscore(int playerId, string gameTitle, int score) diff --git a/Horse Isle Server/Horse Isle Server/Server/GameDataJson.cs b/Horse Isle Server/Horse Isle Server/Server/GameDataJson.cs index c63b615..1bcd70e 100644 --- a/Horse Isle Server/Horse Isle Server/Server/GameDataJson.cs +++ b/Horse Isle Server/Horse Isle Server/Server/GameDataJson.cs @@ -550,6 +550,17 @@ namespace HISP.Server Messages.NoPitchforkMeta = gameData.messages.meta.hay_pile.no_pitchfork; Messages.HasPitchforkMeta = gameData.messages.meta.hay_pile.pitchfork; + // Highscore + + Messages.HighscoreHeaderMeta = gameData.messages.meta.highscores.header_meta; + Messages.HighscoreFormat = gameData.messages.meta.highscores.highscore_format; + Messages.BestTimeFormat = gameData.messages.meta.highscores.besttime_format; + + Messages.GameHighScoreHeaderFormat = gameData.messages.meta.highscores.game_highscore_header; + Messages.GameHighScoreFormat = gameData.messages.meta.highscores.game_highscore_format; + + Messages.GameBestTimeHeaderFormat = gameData.messages.meta.highscores.game_besttime_header; + Messages.GameBestTimeFormat = gameData.messages.meta.highscores.game_besttime_format; // Sec Codes diff --git a/Horse Isle Server/Horse Isle Server/Server/GameServer.cs b/Horse Isle Server/Horse Isle Server/Server/GameServer.cs index 4df020d..e896c5c 100644 --- a/Horse Isle Server/Horse Isle Server/Server/GameServer.cs +++ b/Horse Isle Server/Horse Isle Server/Server/GameServer.cs @@ -158,6 +158,10 @@ namespace HISP.Server metaPacket = PacketBuilder.CreateMetaPacket(Meta.BuildPrivateNotes(sender.LoggedinUser)); sender.SendPacket(metaPacket); break; + case 20: + metaPacket = PacketBuilder.CreateMetaPacket(Meta.BuildMinigameRankingsForUser(sender.LoggedinUser)); + sender.SendPacket(metaPacket); + break; default: Logger.ErrorPrint("Dynamic button #" + buttonId + " unknown..."); break; @@ -349,7 +353,7 @@ namespace HISP.Server return; } - bool newHighscore = Highscore.RegisterHighscore(sender.LoggedinUser.Id, gameTitle, value, time); + bool newHighscore = sender.LoggedinUser.Highscores.UpdateHighscore(gameTitle, value, time); if (newHighscore && !time) { byte[] chatPacket = PacketBuilder.CreateChat(Messages.FormatHighscoreBeatenMessage(value), PacketBuilder.CHAT_BOTTOM_RIGHT); @@ -530,7 +534,21 @@ namespace HISP.Server return; } } - + else if(method == PacketBuilder.PLAYERINFO_HIGHSCORES_LIST) + { + string packetStr = Encoding.UTF8.GetString(packet); + string gameName = packetStr.Substring(2, packetStr.Length - 4); + byte[] metaTag = PacketBuilder.CreateMetaPacket(Meta.BuildTopHighscores(gameName)); + sender.SendPacket(metaTag); + } + else if (method == PacketBuilder.PLAYERINFO_BESTTIMES_LIST) + { + string packetStr = Encoding.UTF8.GetString(packet); + string gameName = packetStr.Substring(2, packetStr.Length - 4); + byte[] metaTag = PacketBuilder.CreateMetaPacket(Meta.BuildTopTimes(gameName)); + sender.SendPacket(metaTag); + } + } public static void OnMovementPacket(GameClient sender, byte[] packet) @@ -1728,7 +1746,7 @@ namespace HISP.Server byte[] loginMessageBytes = PacketBuilder.CreateChat(Messages.FormatLoginMessage(sender.LoggedinUser.Username), PacketBuilder.CHAT_BOTTOM_LEFT); foreach (GameClient client in ConnectedClients) if (client.LoggedIn) - if (!client.LoggedinUser.MuteLogins || client.LoggedinUser.MuteAll) + if (!client.LoggedinUser.MuteLogins && !client.LoggedinUser.MuteAll) if (client.LoggedinUser.Id != userId) client.SendPacket(loginMessageBytes); @@ -1747,7 +1765,6 @@ namespace HISP.Server public static void OnDisconnect(GameClient sender) { connectedClients.Remove(sender); - Logger.DebugPrint("owoo disconnect"); if (sender.LoggedIn) { Database.RemoveOnlineUser(sender.LoggedinUser.Id); @@ -1755,7 +1772,7 @@ namespace HISP.Server byte[] logoutMessageBytes = PacketBuilder.CreateChat(Messages.FormatLogoutMessage(sender.LoggedinUser.Username), PacketBuilder.CHAT_BOTTOM_LEFT); foreach (GameClient client in ConnectedClients) if (client.LoggedIn) - if (!client.LoggedinUser.MuteLogins) + if (!client.LoggedinUser.MuteLogins && !client.LoggedinUser.MuteAll) if (client.LoggedinUser.Id != sender.LoggedinUser.Id) client.SendPacket(logoutMessageBytes); // Tell clients of diconnect (remove from chat) diff --git a/Horse Isle Server/Horse Isle Server/Server/PacketBuilder.cs b/Horse Isle Server/Horse Isle Server/Server/PacketBuilder.cs index 089a1fa..33d561a 100644 --- a/Horse Isle Server/Horse Isle Server/Server/PacketBuilder.cs +++ b/Horse Isle Server/Horse Isle Server/Server/PacketBuilder.cs @@ -40,18 +40,22 @@ namespace HISP.Server public const byte PACKET_PLAYERINFO = 0x16; public const byte PACKET_INFORMATION = 0x28; + public const byte SECCODE_QUEST = 0x32; public const byte SECCODE_ITEM = 0x28; public const byte SECCODE_SCORE = 0x3D; public const byte SECCODE_TIME = 0x3E; - public const byte SECCODE_MONEY = 0x1E; + public const byte SECCODE_MONEY = 0x1E; + public const byte NPC_START_CHAT = 0x14; public const byte NPC_CONTINUE_CHAT = 0x15; public const byte PLAYERINFO_LEAVE = 0x16; public const byte PLAYERINFO_UPDATE_OR_CREATE = 0x15; - + public const byte PLAYERINFO_HIGHSCORES_LIST = 0x51; + public const byte PLAYERINFO_BESTTIMES_LIST = 0x52; + public const byte VIEW_PROFILE = 0x14; public const byte SAVE_PROFILE = 0x15;