From 5ce19eed8a62079b6549e65ec922042451a90185 Mon Sep 17 00:00:00 2001 From: LaoSparrow Date: Tue, 20 Jun 2023 15:58:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20death=20ranking=20(=E6=AD=BB=E4=BA=A1?= =?UTF-8?q?=E6=8E=92=E8=A1=8C=E6=A6=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeathRanking/DeathRanking.cs | 103 ++++++++++++++++++ .../PrismBotTShockAdapter.cs | 71 +++++++++++- PrismBotTShockAdapter/Utils.cs | 9 ++ 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 PrismBot/InternalPlugins/DeathRanking/DeathRanking.cs create mode 100644 PrismBotTShockAdapter/Utils.cs diff --git a/PrismBot/InternalPlugins/DeathRanking/DeathRanking.cs b/PrismBot/InternalPlugins/DeathRanking/DeathRanking.cs new file mode 100644 index 0000000..becae2c --- /dev/null +++ b/PrismBot/InternalPlugins/DeathRanking/DeathRanking.cs @@ -0,0 +1,103 @@ +using System.Text; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using PrismBot.SDK; +using PrismBot.SDK.Data; +using PrismBot.SDK.Extensions; +using PrismBot.SDK.Interfaces; +using PrismBot.SDK.Static; +using Sora.EventArgs.SoraEvent; + +namespace PrismBot.InternalPlugins.DeathRanking; + +public class DeathRanking : Plugin +{ + public override string GetPluginName() => "Death Ranking"; + + public override string GetVersion() => "1.0.0"; + + public override string GetAuthor() => "LaoSparrow"; + + public override string GetDescription() => "死亡排行榜"; + + public override void OnLoad() + { + CommandManager.RegisterGroupCommand(this, new RankingGroupCommand()); + } +} + +public class RankingGroupCommand : IGroupCommand +{ + private const int PAGE_SIZE = 10; + + public string GetCommand() => "死亡排行榜"; + + public string GetPermission() => "deathranking.list"; + + public async Task OnPermissionDeniedAsync(string type, GroupMessageEventArgs eventArgs) + { + await eventArgs.SendDefaultPermissionDeniedMessageAsync(); + } + + public async Task OnPermissionGrantedAsync(string type, GroupMessageEventArgs eventArgs) + { + var args = eventArgs.Message.GetCommandArgs(); + if (args.Length < 2) + { + await eventArgs.SourceGroup.SendGroupMessage("用法:死亡排行榜 <服务器标识符> [页数]"); + return; + } + + var currentPage = 1; + if (args.Length >= 3 && !int.TryParse(args[2], out currentPage)) + { + await eventArgs.SourceGroup.SendGroupMessage("页数需为数字并大于0"); + return; + } + if (currentPage <= 0) + { + await eventArgs.SourceGroup.SendGroupMessage("页数需为数字并大于0"); + return; + } + + await using var db = new BotDbContext(); + var server = await db.Servers.FirstOrDefaultAsync(x => x.Identity == args[1]); + if (server == null) + { + await eventArgs.SourceGroup.SendGroupMessage($"未找到标识符为 {args[1]} 的服务器。"); + return; + } + + var result = await server.SendGetToEndpointAsync("prismbot/death_ranking", new Dictionary + { + { "token", server.Token } + }); + if (result.Ranking == null) + { + await eventArgs.SourceGroup.SendGroupMessage($"服务器发生内部错误 ({nameof(result.Ranking)}值为空)"); + return; + } + + var sb = new StringBuilder(); + sb.AppendFormat("服务器: {0}({1})\n", server.ServerName, server.Identity); + sb.Append("---死亡排行榜---\n"); + foreach (var r in result.Ranking.Skip((currentPage - 1) * PAGE_SIZE).Take(PAGE_SIZE)) + { + sb.AppendFormat("{0}: {1}\n", r.PlayerName, r.DeathCount); + } + + sb.AppendFormat("---页: <{0}/{1}>---", currentPage, result.Ranking.Length / PAGE_SIZE + 1); + await eventArgs.SourceGroup.SendGroupMessage(sb.ToString()); + } + + public class DeathRankingRespond + { + public class DeathRankingRecord + { + public string PlayerName { get; set; } + public int DeathCount { get; set; } + } + + [JsonPropertyName("ranking")] public DeathRankingRecord[]? Ranking { get; set; } + } +} \ No newline at end of file diff --git a/PrismBotTShockAdapter/PrismBotTShockAdapter.cs b/PrismBotTShockAdapter/PrismBotTShockAdapter.cs index 5499f26..67bd3db 100644 --- a/PrismBotTShockAdapter/PrismBotTShockAdapter.cs +++ b/PrismBotTShockAdapter/PrismBotTShockAdapter.cs @@ -1,9 +1,11 @@ -using Newtonsoft.Json; +using MySql.Data.MySqlClient; +using Newtonsoft.Json; using PrismBotTShockAdapter.Models; using Rests; using Terraria; using TerrariaApi.Server; using TShockAPI; +using TShockAPI.DB; namespace PrismBotTShockAdapter; @@ -38,7 +40,7 @@ public override void Initialize() JsonConvert.SerializeObject(new Config())); TShock.Log.ConsoleWarn("未找到配置文件(tshock/PrismBot/config.json),已自动生成"); } - + var elegantWhitelistPath = Path.Combine(AppContext.BaseDirectory, "tshock", "PrismBot", "elegantWhitelist.json"); if (!File.Exists(elegantWhitelistPath)) { @@ -48,8 +50,26 @@ public override void Initialize() TShock.Log.ConsoleWarn("未找到配置文件(tshock/PrismBot/elegantWhitelist.json),已自动生成"); } + + // Database Initialization + + var tableCreator = new SqlTableCreator( + TShock.DB, + TShock.DB.GetSqlType() == SqlType.Sqlite ? new SqliteQueryCreator() : new MysqlQueryCreator()); + + var deathRankingTable = new SqlTable("PB_DeathRanking", + new SqlColumn("AccountID", MySqlDbType.Int32) { Primary = true }, + new SqlColumn("DeathCount", MySqlDbType.Int32)); + tableCreator.EnsureTableStructure(deathRankingTable); + + + // Registration and Hooking + ServerApi.Hooks.ServerJoin.Register(this, OnJoin); TShock.RestApi.Register("/player/info", OnPlayerInventory); + + TShockAPI.GetDataHandlers.KillMe.Register(OnPlayerDeath); + TShock.RestApi.Register("/prismbot/death_ranking", OnRestDeathRanking); } private async void OnJoin(JoinEventArgs args) @@ -107,4 +127,51 @@ private object OnPlayerInventory(RestRequestArgs args) {"inventory", playerInfo.inventory} }; } + + #region Death Ranking + + private void OnPlayerDeath(object? sender, GetDataHandlers.KillMeEventArgs e) + { + try + { + var player = TShock.Players[e.PlayerId]; + if (player.Account == null) + return; + + TShock.DB.Query( + Utils.SwitchDBQuery( + "INSERT INTO PB_DeathRanking (AccountID, DeathCount) VALUES (@0, 1) ON DUPLICATE KEY UPDATE DeathCount=DeathCount+1", + "INSERT INTO PB_DeathRanking (AccountID, DeathCount) VALUES (@0, 1) ON CONFLICT(AccountID) DO UPDATE SET DeathCount=DeathCount+1"), + player.Account.ID); + } + catch (Exception ex) + { + TShock.Log.ConsoleWarn($"[PrismBotAdapter] Exception occur at {nameof(OnPlayerDeath)}, Ex:\n{ex}"); + } + } + + private object OnRestDeathRanking(RestRequestArgs args) + { + var ranking = new List(); + using (var reader = + TShock.DB.QueryReader( + "SELECT Users.Username AS Username, DeathCount FROM PB_DeathRanking INNER JOIN Users ON PB_DeathRanking.AccountID=Users.ID ORDER BY DeathCount DESC")) + { + while (reader.Read()) + { + ranking.Add(new + { + PlayerName = reader.Get("Username"), + DeathCount = reader.Get("DeathCount") + }); + } + } + + return new RestObject + { + { "ranking", ranking } + }; + } + + #endregion } \ No newline at end of file diff --git a/PrismBotTShockAdapter/Utils.cs b/PrismBotTShockAdapter/Utils.cs new file mode 100644 index 0000000..295d600 --- /dev/null +++ b/PrismBotTShockAdapter/Utils.cs @@ -0,0 +1,9 @@ +using TShockAPI; + +namespace PrismBotTShockAdapter; + +public static class Utils +{ + public static string SwitchDBQuery(string mysqlQuery, string sqliteQuery) => + TShock.Config.Settings.StorageType.ToLower() == "mysql" ? mysqlQuery : sqliteQuery; +} \ No newline at end of file