diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 21f12f6..e37e725 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -2,6 +2,8 @@ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net name: YongAnFrame Dev CI +permissions: + contents: read on: push: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index ee5ae43..fb0c1c0 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -2,6 +2,8 @@ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net name: YongAnFrame Master CI +permissions: + contents: read on: push: diff --git a/Commands/Chats/AChatCommand.cs b/Commands/Chats/AChatCommand.cs new file mode 100644 index 0000000..51882d4 --- /dev/null +++ b/Commands/Chats/AChatCommand.cs @@ -0,0 +1,39 @@ +using CommandSystem; +using Exiled.API.Features; +using System; +using System.Linq; +using YongAnFrame.Features.Players; +using YongAnFrame.Features.UI.Enums; +using YongAnFrame.Features.UI.Texts; + +namespace SyncPlugin.Commands.Chats +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class AChatCommand : ICommand + { + public string Command => "achat"; + + public string[] Aliases => ["ac"]; + + public string Description => ""; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (arguments.Array.Length >= 2) + { + if (Player.TryGet(sender, out Player player)) + { + foreach (var player1 in FramePlayer.List.Where((p) => { return p.ExPlayer.RemoteAdminAccess; })) + { + player1.UI.MessageList.Add(new MessageText(arguments.Array[1], 30, MessageType.Feedback)); + } + + response = "OK"; + return true; + } + } + response = "NO"; + return false; + } + } +} diff --git a/Commands/Chats/BChatCommand.cs b/Commands/Chats/BChatCommand.cs new file mode 100644 index 0000000..6ef4f8d --- /dev/null +++ b/Commands/Chats/BChatCommand.cs @@ -0,0 +1,47 @@ +using CommandSystem; +using Exiled.API.Features; +using System; +using YongAnFrame.Extensions; +using YongAnFrame.Features.Players; +using YongAnFrame.Features.UI.Enums; +using YongAnFrame.Features.UI.Texts; + +namespace SyncPlugin.Commands.Chats +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class BChatCommand : ICommand + { + public string Command => "bchat"; + + public string[] Aliases => ["bc"]; + + public string Description => ""; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (arguments.Array.Length == 2) + { + if (Player.TryGet(sender, out Player player)) + { + FramePlayer framePlayer = player.ToFPlayer(); + + //if (framePlayer.IsChatBan) + //{ + // response = "无法发送聊天,你已收到临时聊天禁令"; + // return false; + //} + + foreach (var item in FramePlayer.List) + { + item.UI.ChatList.Add(new ChatText(arguments.Array[1], 10, ChatType.All, framePlayer)); + } + + response = "OK"; + return true; + } + } + response = "NO"; + return false; + } + } +} diff --git a/Commands/Chats/CChatCommand.cs b/Commands/Chats/CChatCommand.cs new file mode 100644 index 0000000..b8a2638 --- /dev/null +++ b/Commands/Chats/CChatCommand.cs @@ -0,0 +1,37 @@ +using CommandSystem; +using Exiled.API.Features; +using System; +using YongAnFrame.Extensions; +using YongAnFrame.Features.UI.Enums; +using YongAnFrame.Features.UI.Texts; + +namespace SyncPlugin.Commands.Chats +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class CChatCommand : ICommand + { + public string Command => "cchat"; + + public string[] Aliases => ["cc"]; + + public string Description => ""; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (arguments.Array.Length == 2) + { + if (Player.TryGet(sender, out Player player)) + { + foreach (var player1 in Player.Get(player.Role.Side)) + { + player1.ToFPlayer().UI.ChatList.Add(new ChatText(arguments.Array[1], 10, ChatType.Team, player.ToFPlayer())); + } + response = "OK"; + return true; + } + } + response = "NO"; + return false; + } + } +} diff --git a/Commands/Chats/ChatBanCommand.cs b/Commands/Chats/ChatBanCommand.cs new file mode 100644 index 0000000..b441879 --- /dev/null +++ b/Commands/Chats/ChatBanCommand.cs @@ -0,0 +1,38 @@ +using CommandSystem; +using Exiled.Permissions.Extensions; +using System; +using YongAnFrame.Features.Players; + +namespace SyncPlugin.Commands.Chats +{ + [CommandHandler(typeof(RemoteAdminCommandHandler))] + public class ChatBanCommand : ICommand + { + public string Command => "chat_ban"; + + public string[] Aliases => ["cb"]; + + public string Description => "用于Ban Chat"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + response = "NO"; + if (sender.CheckPermission("yongan404.chat.ban")) + { + if (arguments.Array.Length > 1) + { + FramePlayer? framePlayer = FramePlayer.Get(int.Parse(arguments.Array[1])); + //framePlayer.IsChatBan = true; + response = "OK"; + return true; + } + } + else + { + response = "请保证你有yongan404.chat.ban权限"; + } + + return false; + } + } +} diff --git a/Commands/ExpCommand.cs b/Commands/ExpCommand.cs index 419ec5a..53b34e4 100644 --- a/Commands/ExpCommand.cs +++ b/Commands/ExpCommand.cs @@ -1,4 +1,4 @@ -using CommandSystem; +using CommandSystem; using Exiled.API.Features; using Exiled.Permissions.Extensions; using System; @@ -19,7 +19,7 @@ public class ExpCommand : ICommand /// /// 次要指令名 /// - public string[] Aliases => ["pexp","pe"]; + public string[] Aliases => ["pexp", "pe"]; /// /// 指令描述 /// @@ -39,7 +39,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s { if (arguments.Count >= 1) { - Player.Get(arguments.Array[1]).ToFPlayer().Level += ulong.Parse(arguments.Array[2]); + Player.Get(arguments.Array[1]).ToFPlayer().AddExp(ulong.Parse(arguments.Array[2]), "管理员签发"); response = "OK"; return true; } diff --git a/Commands/KillCommand.cs b/Commands/KillCommand.cs new file mode 100644 index 0000000..7e0b833 --- /dev/null +++ b/Commands/KillCommand.cs @@ -0,0 +1,29 @@ +using CommandSystem; +using Exiled.API.Features; +using PlayerStatsSystem; +using System; + +namespace SyncPlugin.Commands +{ + [CommandHandler(typeof(ClientCommandHandler))] + public class KillCommand : ICommand + { + public string Command => "killme"; + + public string[] Aliases => ["kill", "s"]; + + public string Description => "自杀指令"; + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (Player.TryGet(sender, out Player player)) + { + player.Kill(new CustomReasonDamageHandler("都死了啦,都你害啦,死亡原因自杀")); + response = "Ok"; + return true; + } + response = "No"; + + return false; + } + } +} diff --git a/Commands/MessageCommand.cs b/Commands/MessageCommand.cs index 1d50525..5b44971 100644 --- a/Commands/MessageCommand.cs +++ b/Commands/MessageCommand.cs @@ -3,7 +3,6 @@ using Exiled.Permissions.Extensions; using System; using System.Collections.Generic; -using System.Runtime.ConstrainedExecution; using YongAnFrame.Extensions; using YongAnFrame.Features.Players; using YongAnFrame.Features.UI.Enums; @@ -78,9 +77,9 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s return false; } - foreach (FramePlayer yPlayer in choicePlayer) + foreach (FramePlayer framePlayer in choicePlayer) { - yPlayer.UI.MessageList.Add(new MessageText($"{arguments.Array[2]}", duration, MessageType.Admin)); + framePlayer.UI.MessageList.Add(new MessageText($"{arguments.Array[2]}", duration, MessageType.Admin)); } response = "已成功运行"; return true; diff --git a/Commands/PlayerCommand.cs b/Commands/PlayerCommand.cs index ff86f0b..f77aa26 100644 --- a/Commands/PlayerCommand.cs +++ b/Commands/PlayerCommand.cs @@ -1,8 +1,6 @@ using CommandSystem; using Exiled.API.Features; using System; -using YongAnFrame.Extensions; -using YongAnFrame.Features.Players; namespace YongAnFrame.Commands { @@ -15,7 +13,7 @@ public class PlayerCommand : ICommand /// public string Command => "FramePlayer"; /// - public string[] Aliases => ["player", "fp" ,"p"]; + public string[] Aliases => ["player", "fp", "p"]; /// public string Description => "用于管理自己的YongAnFrame用户"; /// diff --git a/Commands/TitleCommand.cs b/Commands/TitleCommand.cs new file mode 100644 index 0000000..a2c2d0d --- /dev/null +++ b/Commands/TitleCommand.cs @@ -0,0 +1,110 @@ +using CommandSystem; +using Exiled.Permissions.Extensions; +using System; +using YongAnFrame.Features.Players; + +namespace SyncPlugin.Commands +{ + [CommandHandler(typeof(RemoteAdminCommandHandler))] + public class TitleCommand : ICommand + { + public string Command => "title"; + + public string[] Aliases => ["tit", "ti"]; + + public string Description => "title指令"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + PlayerTitle? titleData; + switch (arguments.Array[1]) + { + case "new": + if (!sender.CheckPermission("yongan404.title.new")) + { + response = "请保证你有yongan404.title.new权限"; + return false; + } + + if (arguments.Array[2] == "dynamic") + { + titleData = new(0, "", "", bool.Parse(arguments.Array[3]), arguments.Array[4]); + titleData.Insert(arguments.Array[4]); + } + else + { + titleData = new(0, arguments.Array[2], arguments.Array[3], bool.Parse(arguments.Array[4])); + titleData.Insert(null); + } + response = "OK"; + return true; + case "up": + if (!sender.CheckPermission("yongan404.title.update")) + { + response = "请保证你有yongan404.title.update权限"; + return false; + } + + titleData = PlayerTitle.Get(uint.Parse(arguments.Array[2])); + if (titleData is null) + { + response = "无效的称号ID"; + return false; + } + + switch (arguments.Array[3]) + { + case "name": + titleData.Name = arguments.Array[4]; + break; + case "color": + titleData.Color = arguments.Array[4]; + break; + case "pro": + titleData.IsRank = bool.Parse(arguments.Array[4]); + break; + case "dc": + titleData.SetDynamicCommand(arguments.Array[4]); + break; + } + titleData.Update(); + response = "OK"; + return true; + case "set": + if (!sender.CheckPermission("yongan404.title.set")) + { + response = "请保证你有yongan404.title.set权限"; + return false; + } + + FramePlayer? framePlayer = FramePlayer.Get(int.Parse(arguments.Array[2])); + titleData = PlayerTitle.Get(uint.Parse(arguments.Array[3])); + + if (framePlayer is null) + { + response = "无效的玩家ID"; + return false; + } + if (titleData is null) + { + response = "无效的称号ID"; + return false; + } + + if (titleData.IsRank) + { + framePlayer.UsingRankTitles = titleData; + } + else + { + framePlayer.UsingTitles = titleData; + } + framePlayer.UpdateShowInfo(); + response = "OK"; + return true; + } + response = "???"; + return false; + } + } +} diff --git a/Config.cs b/Config.cs index 295a27b..49c4c27 100644 --- a/Config.cs +++ b/Config.cs @@ -1,4 +1,4 @@ -using Exiled.API.Interfaces; +using Exiled.API.Interfaces; using System.Collections.Generic; using System.ComponentModel; using YongAnFrame.Features.Roles; @@ -14,6 +14,21 @@ public sealed class Config : IConfig public bool IsEnabled { get; set; } = true; /// public bool Debug { get; set; } + + [Description("是否MySql数据库异常时踢出玩家")] + public bool IsMySqlErrorKick { get; set; } = true; + [Description("MySql数据库服务器")] + public string MySqlServer { get; set; } = "localhost"; + + [Description("MySql数据库用户")] + public string MySqlUser { get; set; } = "root"; + + [Description("MySql数据库密码")] + public string MySqlPassword { get; set; } = "root"; + + [Description("MySql数据库数据表名称")] + public string MySqlDatabaseName { get; set; } = "test"; + /// /// 获取或设置全局的经验加成 /// @@ -28,5 +43,14 @@ public sealed class Config : IConfig /// [Description("禁用自定义角色生成(只能用于继承CustomRolePlus的类)")] public List DisableCustomRolePlus { get; set; } = ["114514", "1"]; + + /// + /// 获取或设置禁用指令 + /// + /// + /// 只能用于继承的类 + /// + [Description("禁用指令")] + public List DisableCommand { get; set; } = ["AChatCommand", "1"]; } } diff --git a/Features/MusicManager.cs b/Features/MusicManager.cs index 92ae08e..ff1ebf2 100644 --- a/Features/MusicManager.cs +++ b/Features/MusicManager.cs @@ -35,7 +35,7 @@ private static void TrackLoaded(TrackLoadedEventArgs args) { if (trackEventDic.TryGetValue(args.VoicePlayerBase, out TrackEvent trackEvent)) { - trackEvent.PlayMusicAction?.Invoke(args); + trackEvent.PlayMusicAction?.Invoke(args); } } @@ -45,7 +45,7 @@ private static void FinishedTrack(TrackFinishedEventArgs args) { trackEvent.StopMusicAction?.Invoke(args); } - + } private static ReferenceHub CreateMusicNpc(string name) @@ -149,11 +149,11 @@ public static VoicePlayerBase Play(string musicName, string npcName, TrackEvent? { if (distance == 0) { - voicePlayerBase.BroadcastTo.Add(npc.PlayerId); + voicePlayerBase.BroadcastTo.Add(npc); } else { - voicePlayerBase.BroadcastTo = [.. FramePlayer.List.Where(p => Vector3.Distance(p.ExPlayer!.Position, source.ExPlayer!.Position) <= distance).Select((s) => s.ExPlayer!.Id)]; + voicePlayerBase.BroadcastTo = [.. FramePlayer.List.Where(p => Vector3.Distance(p.ExPlayer!.Position, source.ExPlayer!.Position) <= distance).Select((s) => s)]; } } @@ -161,9 +161,9 @@ public static VoicePlayerBase Play(string musicName, string npcName, TrackEvent? { foreach (var player in extraPlay) { - if (!voicePlayerBase.BroadcastTo.Contains(player.ExPlayer.Id)) + if (!voicePlayerBase.BroadcastTo.Contains(player)) { - voicePlayerBase.BroadcastTo.Add(player.ExPlayer.Id); + voicePlayerBase.BroadcastTo.Add(player); } } } diff --git a/Features/PlayerUI.cs b/Features/PlayerUI.cs index 0ae1bfb..ba165c3 100644 --- a/Features/PlayerUI.cs +++ b/Features/PlayerUI.cs @@ -1,4 +1,5 @@ -using HintServiceMeow.Core.Enum; +using Exiled.API.Features.Waves; +using HintServiceMeow.Core.Enum; using HintServiceMeow.Core.Models.Hints; using HintServiceMeow.Core.Utilities; using MEC; @@ -37,7 +38,7 @@ public class PlayerUI #region Hint private readonly Hint versionHint = new() { - Text = "YongAnFrame 1.0.0-beta6+002", + Text = "YongAnFrame 1.0.0-beta8", FontSize = 20, Alignment = HintAlignment.Center, YCoordinateAlign = HintVerticalAlign.Top, @@ -54,13 +55,17 @@ public class PlayerUI { FontSize = 20, Alignment = HintAlignment.Right, - YCoordinate = 400 + YCoordinate = 600 }; private readonly Hint messageHint = new() { FontSize = 20, Alignment = HintAlignment.Left, - YCoordinate = 400 + YCoordinate = 600 + }; + public readonly Hint waveTimerHint = new() + { + YCoordinate = 190 }; #endregion @@ -68,7 +73,7 @@ private IEnumerator Timer() { while (true) { - + UpdateWaveTimerUI(); for (int i = 0; i < MessageList.Count; i++) { @@ -78,8 +83,8 @@ private IEnumerator Timer() MessageList.Remove(message); i--; } - UpdateMessageUI(); } + UpdateMessageUI(); bool isUpdate = false; @@ -107,6 +112,20 @@ public void UpdateUI() UpdateCustomRoleUI(); UpdateMessageUI(); UpdateChatUI(); + UpdateWaveTimerUI(); + } + + public void UpdateWaveTimerUI() + { + if (FPlayer.ExPlayer.IsDead) + { + waveTimerHint.Text = $"下一波九尾狐刷新:{(int)WaveTimer.GetWaveTimers()[0].TimeLeft.TotalSeconds}\n" + + $"下一波混沌刷新:{(int)WaveTimer.GetWaveTimers()[1].TimeLeft.TotalSeconds}"; + } + else + { + waveTimerHint.Text = null; + } } /// @@ -162,12 +181,13 @@ public PlayerUI(FramePlayer fPlayer) FPlayer = fPlayer; MessageList = new(7, UpdateMessageUI); ChatList = new(7, UpdateChatUI); - coroutine = Timing.RunCoroutine(Timer()); - PlayerDisplay = PlayerDisplay.Get(fPlayer); + PlayerDisplay = PlayerDisplay.Get(FPlayer.ExPlayer.ReferenceHub); PlayerDisplay.AddHint(customRoleHint); PlayerDisplay.AddHint(chatHint); PlayerDisplay.AddHint(messageHint); PlayerDisplay.AddHint(versionHint); + PlayerDisplay.AddHint(waveTimerHint); + coroutine = Timing.RunCoroutine(Timer()); } /// /// 解构方法 diff --git a/Features/Players/CustomPlayer.cs b/Features/Players/CustomPlayer.cs index 627ba95..7efc56e 100644 --- a/Features/Players/CustomPlayer.cs +++ b/Features/Players/CustomPlayer.cs @@ -53,7 +53,7 @@ public abstract class CustomPlayer(FramePlayer player) /// /// 隐性转换 /// - /// 自定义玩家 - public static implicit operator FramePlayer(CustomPlayer yPlayer) => yPlayer.FramePlayer; + /// 自定义玩家 + public static implicit operator FramePlayer(CustomPlayer customPlayer) => customPlayer.FramePlayer; } } diff --git a/Features/Players/FramePlayer.cs b/Features/Players/FramePlayer.cs index c3d800f..0eb8831 100644 --- a/Features/Players/FramePlayer.cs +++ b/Features/Players/FramePlayer.cs @@ -1,10 +1,10 @@ -using Exiled.API.Features; -using Exiled.CustomRoles; +using Exiled.API.Features; using Exiled.CustomRoles.API; using Exiled.CustomRoles.API.Features; using Exiled.Events.EventArgs.Player; using Exiled.Events.Features; using MEC; +using MySqlConnector; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -34,9 +34,9 @@ public sealed class FramePlayer : ICustomAlgorithm /// 在运行事件后实例无效,再调用可能会引发异常
/// 玩家退出后必须不再引用,否则会造成数字ID重复的问题 /// - public Player ExPlayer + public Player ExPlayer { - get + get { if (exPlayer is null) { @@ -92,7 +92,12 @@ public CustomRolePlus? CustomRolePlus /// /// 获取或设置玩家的批准绕过DNT /// - public bool IsBDNT { get; set; } + public bool IsBDNT { get; set; } = false; + /// + /// 获取玩家是否无效 + /// + public bool IsInvalid => exPlayer is null; + public List PosTitles { get; private set; } = []; /// /// 获取或设置玩家正在使用的名称称号 /// @@ -182,18 +187,21 @@ public static void SubscribeStaticEvents() ///
public static void UnsubscribeStaticEvents() { - Exiled.Events.Handlers.Player.Verified += new CustomEventHandler(OnStaticVerified); - Exiled.Events.Handlers.Player.Destroying += new CustomEventHandler(OnStaticDestroying); + Exiled.Events.Handlers.Player.Verified -= new CustomEventHandler(OnStaticVerified); + Exiled.Events.Handlers.Player.Destroying -= new CustomEventHandler(OnStaticDestroying); } private static void OnStaticVerified(VerifiedEventArgs args) { - new FramePlayer(args.Player); + Load(args.Player)!.UpdateShowInfo(); } private static void OnStaticDestroying(DestroyingEventArgs args) { FramePlayer fPlayer = args.Player.ToFPlayer(); - fPlayer.Invalid(); + if (!fPlayer.IsInvalid) + { + fPlayer.Invalid(); + } } #endregion @@ -209,7 +217,6 @@ internal FramePlayer(Player player) UI = new(this); CustomAlgorithm = this; Events.Handlers.FramePlayer.OnFramePlayerCreated(new FramePlayerCreatedEventArgs(this)); - UpdateShowInfo(); } /// @@ -386,11 +393,11 @@ public static FramePlayer Get(Player? player) { throw new InvalidCastException("Player实例无效"); } - if (!dictionary.TryGetValue(player.Id, out FramePlayer yPlayer)) + if (!dictionary.TryGetValue(player.Id, out FramePlayer framePlayer)) { throw new InvalidCastException("FramePlayer实例无效"); } - return yPlayer; + return framePlayer; } /// @@ -400,6 +407,147 @@ public static FramePlayer Get(Player? player) /// 框架玩家 public static FramePlayer Get(int numId) => Get(Player.Get(numId)); + /// + /// 不推荐直接访问,请尝试使用ToFPlayer() + /// + /// + /// + public static FramePlayer? Load(Player player) + { + if (player.IsNPC) + { + return new(player) + { + Level = 1, + Exp = 0, + PosTitles = [], + UsingTitles = null, + UsingRankTitles = null, + }; + } + + FramePlayer? framePlayer = null; + + string queryString = "select * from player_data where Id = @Id"; + try + { + using MySqlConnection connection = new(YongAnFramePlugin.Instance.ConnectionString); + connection.Open(); + using MySqlCommand command = new(queryString, connection); + command.Parameters.AddWithValue("Id", player.UserId); + using MySqlDataReader reader = command.ExecuteReader(); + if (reader.Read()) + { + List posTitles = []; + if (reader["PosTitles"].ToString() != "") + { + foreach (string posTitleIdString in reader["PosTitles"].ToString().Split(',')) + { + PlayerTitle? title = PlayerTitle.Get(uint.Parse(posTitleIdString)); + if (title != null) + { + posTitles.Add(title); + } + } + } + + framePlayer = new(player) + { + Level = (ulong)reader["Level"], + Exp = (ulong)reader["Exp"], + ExpMultiplier = (float)reader["ExpMultiplier"], + PosTitles = posTitles, + UsingTitles = PlayerTitle.Get((uint)reader["UsingTitles"]), + UsingRankTitles = PlayerTitle.Get((uint)reader["UsingRankTitles"]), + }; + } + } + catch (Exception text) + { + Log.Error($"数据库查找FramePlayer数据异常({player.UserId}) 错误原因:{text}"); + if (YongAnFramePlugin.Instance.Config.IsMySqlErrorKick) + { + player.Kick("不要慌张!你的数据库数据可能存在异常,为了保证你的游戏数据不被覆盖,你已被踢出服务器!\n请将错误联系到管理员(数据库查找FramePlayer数据异常)"); + } + return null; + } + + if (framePlayer == null) + { + framePlayer = new(player) + { + Level = 1, + Exp = 0, + ExpMultiplier = 1, + PosTitles = [], + UsingTitles = null, + UsingRankTitles = null, + }; + try + { + using MySqlConnection connection = new(YongAnFramePlugin.Instance.ConnectionString); + connection.Open(); + using MySqlCommand cmd = new("insert into player_data set Id=@Id,Level=@Level,Exp=@Exp,ExpMultiplier=@ExpMultiplier,PosTitles=@PosTitles,UsingTitles=@UsingTitles,UsingRankTitles=@UsingRankTitles", connection); + cmd.Parameters.AddWithValue("Id", framePlayer.ExPlayer.UserId); + cmd.Parameters.AddWithValue("Level", framePlayer.Level); + cmd.Parameters.AddWithValue("Exp", framePlayer.Exp); + cmd.Parameters.AddWithValue("ExpMultiplier", framePlayer.ExpMultiplier); + string posTitleString = ""; + foreach (var item in framePlayer.PosTitles) + { + posTitleString += $",{item.Id}"; + } + if (posTitleString.Length > 0) + posTitleString.Remove(0, 1); + + cmd.Parameters.AddWithValue("PosTitles", posTitleString); + cmd.Parameters.AddWithValue("UsingTitles", framePlayer.UsingTitles != null ? framePlayer.UsingTitles.Id : 0); + cmd.Parameters.AddWithValue("UsingRankTitles", framePlayer.UsingRankTitles != null ? framePlayer.UsingRankTitles.Id : 0); + cmd.ExecuteNonQuery(); + } + catch (Exception text) + { + Log.Error("数据库插入数据异常 错误原因:" + text); + return null; + } + } + + return framePlayer; + } + + public bool Save() + { + if (ExPlayer.IsNPC || (!IsBDNT && ExPlayer.DoNotTrack)) return false; + + try + { + using MySqlConnection connection = new(YongAnFramePlugin.Instance.ConnectionString); + connection.Open(); + using MySqlCommand cmd = new("update player_data set Level=@Level,Exp=@Exp,ExpMultiplier=@ExpMultiplier,PosTitles=@PosTitles,UsingTitles=@UsingTitles,UsingRankTitles=@UsingRankTitles where Id=@Id", connection); + cmd.Parameters.AddWithValue("Id", ExPlayer.UserId); + cmd.Parameters.AddWithValue("Exp", Exp); + cmd.Parameters.AddWithValue("ExpMultiplier", ExpMultiplier); + cmd.Parameters.AddWithValue("Level", Level); + string posTitleString = ""; + foreach (var item in PosTitles) + { + posTitleString += $",{item.Id}"; + } + if (posTitleString.Length > 0) + posTitleString.Remove(0, 1); + cmd.Parameters.AddWithValue("PosTitles", posTitleString); + cmd.Parameters.AddWithValue("UsingTitles", UsingTitles != null ? UsingTitles.Id : 0); + cmd.Parameters.AddWithValue("UsingRankTitles", UsingRankTitles != null ? UsingRankTitles.Id : 0); + cmd.ExecuteNonQuery(); + return true; + } + catch (Exception text) + { + Log.Error("数据库数据更新异常 错误原因:" + text); + return false; + } + } + /// /// 调用后该实例会立刻无效
/// 调用后该实例会立刻无效
@@ -407,6 +555,7 @@ public static FramePlayer Get(Player? player) ///
public void Invalid() { + Save(); Events.Handlers.FramePlayer.OnFramePlayerInvalidating(new FramePlayerInvalidatingEventArgs(this)); CustomRolePlus?.RemoveRole(this); dictionary.Remove(ExPlayer.Id); @@ -417,7 +566,14 @@ public void Invalid() /// /// 隐性转换 /// - /// 框架玩家 - public static implicit operator Player(FramePlayer yPlayer) => yPlayer.ExPlayer; + /// 框架玩家 + public static implicit operator Player(FramePlayer framePlayer) => framePlayer.ExPlayer; + + /// + /// 隐性转换 + /// + /// 框架玩家 + public static implicit operator ReferenceHub(FramePlayer framePlayer) => framePlayer; + } } diff --git a/Features/Players/PlayerTitle.cs b/Features/Players/PlayerTitle.cs index 652cee5..ab9d30b 100644 --- a/Features/Players/PlayerTitle.cs +++ b/Features/Players/PlayerTitle.cs @@ -1,4 +1,5 @@ using Exiled.API.Features; +using MySqlConnector; using System; using System.Collections.Generic; @@ -14,10 +15,7 @@ public sealed class PlayerTitle /// 获取有效的玩家称号列表 ///
public static IReadOnlyCollection List => [.. dictionary.Values]; - /// - /// 获取或设置加载称号委托 - /// - public static Func? LoadFunc { get; set; } + /// /// 获取或设置称号的ID /// @@ -81,19 +79,91 @@ public void SetDynamicCommand(string? dynamicCommandString) /// 获取的称号 public static PlayerTitle? Get(uint id) { - if (LoadFunc is null) - { - Log.Error("称号功能无法在框架内获取,请设置PlayerTitle.LoadFunc属性或写个缓存"); - return null; - } - if (dictionary.TryGetValue(id, out PlayerTitle? title)) { return title; } - title = LoadFunc.Invoke(id); if (title != null) dictionary.Add(id, title); return title; } + + public static PlayerTitle? Load(uint Id) + { + string queryString = "select * from title_data where Id = @Id"; + try + { + using MySqlConnection connection = new(YongAnFramePlugin.Instance.ConnectionString); + connection.Open(); + + using MySqlCommand command = new(queryString, connection); + command.Parameters.AddWithValue("@Id", Id); + + using MySqlDataReader reader = command.ExecuteReader(); + if (reader.Read()) + { + return new(Id, reader["Name"].ToString(), reader["Color"].ToString(), (byte)reader["Pro"] != 0, reader["DynamicCommand"].ToString()); + } + return null; + } + catch (Exception text) + { + Log.Error("数据库查找异常 错误原因:" + text); + return null; + } + } + + public bool Insert(string? dynamicCommand) + { + try + { + using MySqlConnection connection = new(YongAnFramePlugin.Instance.ConnectionString); + connection.Open(); + using MySqlCommand cmd = new("insert into title_data set Id=@Id,Name=@Name,Color=@Color,Pro=@Pro,DynamicCommand=@DynamicCommand", connection); + Id = (uint)cmd.LastInsertedId; + cmd.Parameters.AddWithValue("Id", Id); + cmd.Parameters.AddWithValue("Name", Name); + cmd.Parameters.AddWithValue("Color", Color); + cmd.Parameters.AddWithValue("Pro", IsRank ? 1 : 0); + cmd.Parameters.AddWithValue("DynamicCommand", dynamicCommand); + cmd.ExecuteNonQuery(); + return true; + } + catch (Exception text) + { + Log.Error("数据库插入数据异常 错误原因:" + text); + return false; + } + } + public bool Update() + { + try + { + using MySqlConnection connection = new(YongAnFramePlugin.Instance.ConnectionString); + connection.Open(); + using MySqlCommand cmd = new("update title_data set Name=@Name,Color=@Color,Pro=@Pro,DynamicCommand=@DynamicCommand where Id=@Id", connection); + cmd.Parameters.AddWithValue("Id", (uint)cmd.LastInsertedId); + cmd.Parameters.AddWithValue("Name", Name); + cmd.Parameters.AddWithValue("Color", Color); + cmd.Parameters.AddWithValue("Pro", IsRank ? 1 : 0); + string? dynamicString = null; + if (DynamicCommand is not null) + { + foreach (var command in DynamicCommand) + { + dynamicString = string.Join(",", command) + ";"; + } + dynamicString = dynamicString?.Substring(0, dynamicString.Length - 1); + } + + cmd.Parameters.AddWithValue("DynamicCommand", dynamicString); + cmd.ExecuteNonQuery(); + return true; + } + catch (Exception text) + { + Log.Error("数据库数据更新异常 错误原因:" + text); + return false; + } + } } } diff --git a/Features/Roles/CustomRolePlus.cs b/Features/Roles/CustomRolePlus.cs index 8001a56..80bcd48 100644 --- a/Features/Roles/CustomRolePlus.cs +++ b/Features/Roles/CustomRolePlus.cs @@ -1,4 +1,4 @@ -using Exiled.API.Features; +using Exiled.API.Features; using Exiled.API.Features.DamageHandlers; using Exiled.API.Features.Spawn; using Exiled.CustomRoles.API; @@ -10,7 +10,6 @@ using PlayerRoles; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using YongAnFrame.Extensions; using YongAnFrame.Features.Players; using YongAnFrame.Features.Roles.Enums; @@ -29,7 +28,7 @@ public abstract class CustomRolePlus : CustomRole /// /// 不要修改这个值 /// - public override bool IgnoreSpawnSystem { get; set; } = false; + public override bool IgnoreSpawnSystem { get; set; } = true; /// /// 获取禁用的自定义角色生成 /// @@ -136,7 +135,7 @@ public virtual void AddRole(FramePlayer fPlayer) fPlayer.ExPlayer.EnableEffect(Exiled.API.Enums.EffectType.MovementBoost); fPlayer.ExPlayer.ChangeEffectIntensity(Exiled.API.Enums.EffectType.MovementBoost, (byte)((BaseProperties.BaseMovementSpeedMultiplier - 1f) * 100)); } - if (!string.IsNullOrEmpty(SpawnProperties.Info)) Cassie.MessageTranslated($""/*ADMINISTER TEAM DESIGNATED {CASSIEDeathName} HASENTERED*/, SpawnProperties.Info, true, true, true); + if (!string.IsNullOrEmpty(SpawnProperties.Info)) Exiled.API.Features.Cassie.MessageTranslated($""/*ADMINISTER TEAM DESIGNATED {CASSIEDeathName} HASENTERED*/, SpawnProperties.Info, true, true, true); if (!string.IsNullOrEmpty(SpawnProperties.MusicNameName)) { MusicManager.Play(SpawnProperties.MusicNameName!, $"{Name}"); @@ -175,7 +174,7 @@ public override void RemoveRole(Player player) { RemoveRole(player.ToFPlayer()); } - catch (InvalidCastException){ } + catch (InvalidCastException) { } } /// /// 给玩家移除这个角色 @@ -187,7 +186,7 @@ public virtual void RemoveRole(FramePlayer fPlayer) { if (!data.IsDeathHandling) { - Cassie.MessageTranslated($"Died", $"{Name}游玩二游被榨干而死(非常正常死亡)"); + Exiled.API.Features.Cassie.MessageTranslated($"Died", $"{Name}游玩二游被榨干而死(非常正常死亡)"); } base.RemoveRole(fPlayer.ExPlayer); BaseData.Remove(fPlayer); @@ -340,7 +339,7 @@ private void OnDying(DyingEventArgs args) { if (args.Attacker is null) { - Cassie.MessageTranslated($"Died", $"{Name}被充满恶意的游戏环境草飞了"); + Exiled.API.Features.Cassie.MessageTranslated($"Died", $"{Name}被充满恶意的游戏环境草飞了"); data.IsDeathHandling = true; } else @@ -350,16 +349,16 @@ private void OnDying(DyingEventArgs args) CustomRole customRole = args.Attacker.GetCustomRoles()[0]; if (RoleDeathText.TryGetValue(customRole.Id, out string text)) { - Cassie.MessageTranslated($"Died", text.Replace("{Name}", Name).Replace("{Attacker}", customRole.Name)); + Exiled.API.Features.Cassie.MessageTranslated($"Died", text.Replace("{Name}", Name).Replace("{Attacker}", customRole.Name)); } else { - Cassie.MessageTranslated($"Died", $"({Name})被({customRole.Name})斩杀"); + Exiled.API.Features.Cassie.MessageTranslated($"Died", $"({Name})被({customRole.Name})斩杀"); } } else { - Cassie.MessageTranslated($"Died", $"({Name})被({args.Attacker.Nickname})斩杀"); + Exiled.API.Features.Cassie.MessageTranslated($"Died", $"({Name})被({args.Attacker.Nickname})斩杀"); } } data.IsDeathHandling = true; diff --git a/Patch/AddLogPatch.cs b/Patch/AddLogPatch.cs deleted file mode 100644 index 6231603..0000000 --- a/Patch/AddLogPatch.cs +++ /dev/null @@ -1,65 +0,0 @@ -using HarmonyLib; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using YongAnFrame.Features; - -namespace YongAnFrame.Patch -{ - /// - /// 在添加的补丁 - /// - [HarmonyPatch(typeof(ServerConsole), nameof(ServerConsole.AddLog))] - public static class AddLogPatch - { -#pragma warning disable IDE0060 // 删除未使用的参数 - private static void Prefix(string q, ConsoleColor color = ConsoleColor.Gray, bool hideFromOutputs = false) -#pragma warning restore IDE0060 // 删除未使用的参数 - { - SaveLog(q, new StackTrace()); - } - - private static readonly Queue logQueue = new(); - private static readonly Task logTask = new(async () => - { - while (true) - { - while (logQueue.Count != 0) - { - InfoData infoData = logQueue.Dequeue(); - using StreamWriter writer = new($"{PathManager.Log}/{DateTime.Now:yyyy-MM-dd}.log", true, Encoding.UTF8); - writer.WriteLine(infoData); - } - await Task.Delay(1000); - } - }); - - /// - /// 启动日志任务 - /// - public static void StartTask() - { - if (logTask.Status == TaskStatus.Created) - { - logTask.Start(); - } - } - - private static void SaveLog(string log, StackTrace trace) - { - logQueue.Enqueue(new InfoData(log, trace)); - } - - private readonly struct InfoData(string content, StackTrace trace) - { - public string Content { get; } = content; - public StackTrace StackTrace { get; } = trace; - public override readonly string ToString() => $"{Content}::{StackTrace}"; - public static implicit operator string(InfoData data) => data.ToString(); - } - } -} diff --git a/README.md b/README.md index 051e850..791c9cb 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,22 @@ # YongAnFrame [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FSCP-SL-Plugin-YongAnTeam%2FYongAnFrame.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FSCP-SL-Plugin-YongAnTeam%2FYongAnFrame?ref=badge_shield) -基于[EXILED](https://github.com/ExSLMod-Team/EXILED)写的框架,为国内服务器常见的插件需求简化开发 +基于[EXILED](https://github.com/ExMod-Team/EXILED)写的框架,为国内服务器常见的插件需求简化开发 ## YongAnFrame是什么类型的插件/框架? -**YongAnFrame** 是一个集成了命令系统、整合了自定义角色及技能系统、称号系统、UI系统等的免费框架,使用 **[EXILED](https://github.com/ExMod-Team/EXILED)** 开发。\ +**YongAnFrame** 是一个整合了自定义角色及技能系统、称号系统、UI系统等的免费框架,使用 **[EXILED](https://github.com/ExMod-Team/EXILED)** 开发。\ 您可以通过安装该插件并使用该框架开发插件,简化您的开发流程。 ## 功能 -- 称号系统:提供动态指令运行集 +- 称号功能:提供名字称号和地位称号,支持动态/彩色功能(使用动态运行集) +- 聊天功能:提供ac、bc、cc等常用聊天功能 +- 持久化: 提供数据持久化,使用MySQL存储数据 - DNT检测和BDNT(Bypass DNT)请求:使信息获取和保存符合VSR规则 -- UI系统:使用[HSM](https://github.com/MeowServer/HintServiceMeow)预制一些比较常用的UI +- UI功能:提供一些比较常用的UI方法,使用[HSM](https://github.com/MeowServer/HintServiceMeow) - 自定义角色:提供对[EXILED](https://github.com/ExMod-Team/EXILED)的自定义角色升级,部分兼容[EXILED](https://github.com/ExMod-Team/EXILED)自定义角色 -- 等级系统:提供整合统一的等级API,支持自定义算法 +- 等级功能:提供整合统一的等级API,支持自定义算法 - 自定义算法:提供一个可替换主要算法的属性(该功能不支持多元化,只能采用一个主要算法) -- 音频API:提供更方便调用SCPSLAudioApi +- 音频API:提供更方便调用AudioApi ## 为服务端安装框架 如图,下载 **Release下的最新压缩包** 全部解压并合并到**EXILED目录**,如C:\Users\Administrator\Appdata\Roaming\ ,安装后请启动一次服务端,并在EXILED\Config内根据提示调整配置文件. diff --git a/YongAnFrame.csproj b/YongAnFrame.csproj index beeb5cf..783d12c 100644 --- a/YongAnFrame.csproj +++ b/YongAnFrame.csproj @@ -8,13 +8,12 @@ True zh-Hans False - 1.0.0-beta6 + 1.0.0 + beta8 YongAn404 $(Authors) Copyright © YongAn404 $(AssemblyName) - 1.0.0.0 - 1.0.0.0 disable True LGPL-3.0-only @@ -38,6 +37,7 @@ + @@ -52,9 +52,10 @@ - - - + + + + diff --git a/YongAnFramePlugin.cs b/YongAnFramePlugin.cs index 0fb00fb..4a835f7 100644 --- a/YongAnFramePlugin.cs +++ b/YongAnFramePlugin.cs @@ -1,11 +1,14 @@ -using Exiled.API.Enums; +using CommandSystem; +using Exiled.API.Enums; using Exiled.API.Features; using HarmonyLib; +using RemoteAdmin; using System; +using System.Collections.Generic; +using System.Reflection; using YongAnFrame.Features; using YongAnFrame.Features.Players; using YongAnFrame.Features.Roles; -using YongAnFrame.Patch; namespace YongAnFrame { @@ -18,8 +21,8 @@ public sealed class YongAnFramePlugin : Plugin /// /// 获取单例 /// - public static YongAnFramePlugin Instance - { + public static YongAnFramePlugin Instance + { get { if (instance is null) @@ -29,6 +32,10 @@ public static YongAnFramePlugin Instance return instance; } } + + public string ConnectionString => $"server={Config.MySqlServer}; User Id = {Config.MySqlUser} ;Password = {Config.MySqlPassword} ;Database = {Config.MySqlDatabaseName};Charset = utf8"; + + /// /// 获取实例 /// @@ -45,7 +52,6 @@ public override void OnEnabled() PathManager.CheckPath(); FramePlayer.SubscribeStaticEvents(); CustomRolePlus.SubscribeStaticEvents(); - AddLogPatch.StartTask(); Harmony.PatchAll(); base.OnEnabled(); } @@ -59,5 +65,95 @@ public override void OnDisabled() Harmony.UnpatchAll(); base.OnDisabled(); } + + /// + public override void OnRegisteringCommands() + { + Dictionary> dictionary = []; + Type[] types = Assembly.GetTypes(); + foreach (Type type in types) + { + if (type.GetInterface("ICommand") != typeof(ICommand) || Config.DisableCommand.Contains(type.Name) || !Attribute.IsDefined(type, typeof(CommandHandlerAttribute))) + { + continue; + } + + foreach (CustomAttributeData customAttributesDatum in type.GetCustomAttributesData()) + { + try + { + if (customAttributesDatum.AttributeType != typeof(CommandHandlerAttribute)) + { + continue; + } + + Type type2 = (Type)customAttributesDatum.ConstructorArguments[0].Value; + ICommand command = GetCommand(type) ?? ((ICommand)Activator.CreateInstance(type)); + if (typeof(ParentCommand).IsAssignableFrom(type2)) + { + if (GetCommand(type2) is not ParentCommand parentCommand) + { + if (!dictionary.TryGetValue(type2, out var value)) + { + dictionary.Add(type2, [command]); + } + else + { + value.Add(command); + } + } + else + { + parentCommand.RegisterCommand(command); + } + + continue; + } + + try + { + if (type2 == typeof(RemoteAdminCommandHandler)) + { + CommandProcessor.RemoteAdminCommandHandler.RegisterCommand(command); + } + else if (type2 == typeof(GameConsoleCommandHandler)) + { + GameCore.Console.ConsoleCommandHandler.RegisterCommand(command); + } + else if (type2 == typeof(ClientCommandHandler)) + { + QueryProcessor.DotCommandHandler.RegisterCommand(command); + } + } + catch (ArgumentException ex) + { + if (ex.Message.StartsWith("An")) + { + Log.Error("Command with same name has already registered! Command: " + command.Command); + } + else + { + Log.Error($"An error has occurred while registering a command: {ex}"); + } + } + + Commands[type2][type] = command; + } + catch (Exception arg) + { + Log.Error($"An error has occurred while registering a command: {arg}"); + } + } + } + + foreach (KeyValuePair> item in dictionary) + { + ParentCommand parentCommand2 = GetCommand(item.Key) as ParentCommand; + foreach (ICommand item2 in item.Value) + { + parentCommand2.RegisterCommand(item2); + } + } + } } } diff --git a/bin/Debug/net48/HintServiceMeow-Exiled.dll b/bin/Debug/net48/HintServiceMeow-Exiled.dll new file mode 100644 index 0000000..cb7623d Binary files /dev/null and b/bin/Debug/net48/HintServiceMeow-Exiled.dll differ