diff --git a/Command/Command.csproj b/Command/Command.csproj new file mode 100644 index 00000000..50071390 --- /dev/null +++ b/Command/Command.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + EggLink.DanhengServer.Command + DanhengCommand + + + + + + + + diff --git a/GameServer/Command/Cmd/CommandAvatar.cs b/Command/Command/Cmd/CommandAvatar.cs similarity index 93% rename from GameServer/Command/Cmd/CommandAvatar.cs rename to Command/Command/Cmd/CommandAvatar.cs index 3338c135..408d41d8 100644 --- a/GameServer/Command/Cmd/CommandAvatar.cs +++ b/Command/Command/Cmd/CommandAvatar.cs @@ -80,7 +80,7 @@ namespace EggLink.DanhengServer.Command.Cmd // sync player.SendPacket(new PacketPlayerSyncScNotify(avatar)); - arg.SendMsg(I18nManager.Translate("Game.Command.Avatar.AvatarLevelSet", avatar.Excel?.Name ?? avatarId.ToString(), I18nManager.Translate("Word.Talent"), level.ToString())); + arg.SendMsg(I18nManager.Translate("Game.Command.Avatar.AvatarLevelSet", avatar.Excel?.Name?.Replace("{NICKNAME}", player.Data.Name) ?? avatarId.ToString(), I18nManager.Translate("Word.Talent"), level.ToString())); } [CommandMethod("get")] @@ -153,7 +153,7 @@ namespace EggLink.DanhengServer.Command.Cmd // sync arg.Target.SendPacket(new PacketPlayerSyncScNotify(avatar)); - arg.SendMsg(I18nManager.Translate("Game.Command.Avatar.AvatarLevelSet", avatar.Excel?.Name ?? id.ToString(), I18nManager.Translate("Word.Rank"), rank.ToString())); + arg.SendMsg(I18nManager.Translate("Game.Command.Avatar.AvatarLevelSet", avatar.Excel?.Name?.Replace("{NICKNAME}", arg.Target.Player!.Data.Name) ?? id.ToString(), I18nManager.Translate("Word.Rank"), rank.ToString())); } } @@ -206,7 +206,7 @@ namespace EggLink.DanhengServer.Command.Cmd // sync arg.Target.SendPacket(new PacketPlayerSyncScNotify(avatar)); - arg.SendMsg(I18nManager.Translate("Game.Command.Avatar.AvatarLevelSet", avatar.Excel?.Name ?? id.ToString(), I18nManager.Translate("Word.Avatar"), level.ToString())); + arg.SendMsg(I18nManager.Translate("Game.Command.Avatar.AvatarLevelSet", avatar.Excel?.Name?.Replace("{NICKNAME}", arg.Target.Player!.Data.Name) ?? id.ToString(), I18nManager.Translate("Word.Avatar"), level.ToString())); } } } diff --git a/GameServer/Command/Cmd/CommandGive.cs b/Command/Command/Cmd/CommandGive.cs similarity index 100% rename from GameServer/Command/Cmd/CommandGive.cs rename to Command/Command/Cmd/CommandGive.cs diff --git a/GameServer/Command/Cmd/CommandGiveall.cs b/Command/Command/Cmd/CommandGiveall.cs similarity index 100% rename from GameServer/Command/Cmd/CommandGiveall.cs rename to Command/Command/Cmd/CommandGiveall.cs diff --git a/GameServer/Command/Cmd/CommandHelp.cs b/Command/Command/Cmd/CommandHelp.cs similarity index 83% rename from GameServer/Command/Cmd/CommandHelp.cs rename to Command/Command/Cmd/CommandHelp.cs index 44ae4984..7fe24971 100644 --- a/GameServer/Command/Cmd/CommandHelp.cs +++ b/Command/Command/Cmd/CommandHelp.cs @@ -1,5 +1,4 @@ using EggLink.DanhengServer.Internationalization; -using EggLink.DanhengServer.Program; using System; using System.Collections.Generic; using System.Linq; @@ -14,8 +13,12 @@ namespace EggLink.DanhengServer.Command.Cmd [CommandDefault] public void Help(CommandArg arg) { - var commands = EntryPoint.CommandManager.CommandInfo.Values; + var commands = CommandManager.Instance?.CommandInfo.Values; arg.SendMsg(I18nManager.Translate("Game.Command.Help.Commands")); + if (commands == null) + { + return; + } foreach (var command in commands) { arg.SendMsg($"/{command.Name} - {I18nManager.Translate(command.Description)}\n{I18nManager.Translate("Game.Command.Help.CommandUsage")} {I18nManager.Translate(command.Usage)}"); diff --git a/GameServer/Command/Cmd/CommandHero.cs b/Command/Command/Cmd/CommandHero.cs similarity index 100% rename from GameServer/Command/Cmd/CommandHero.cs rename to Command/Command/Cmd/CommandHero.cs diff --git a/GameServer/Command/Cmd/CommandKick.cs b/Command/Command/Cmd/CommandKick.cs similarity index 100% rename from GameServer/Command/Cmd/CommandKick.cs rename to Command/Command/Cmd/CommandKick.cs diff --git a/GameServer/Command/Cmd/CommandLineup.cs b/Command/Command/Cmd/CommandLineup.cs similarity index 100% rename from GameServer/Command/Cmd/CommandLineup.cs rename to Command/Command/Cmd/CommandLineup.cs diff --git a/Command/Command/Cmd/CommandMail.cs b/Command/Command/Cmd/CommandMail.cs new file mode 100644 index 00000000..4564e551 --- /dev/null +++ b/Command/Command/Cmd/CommandMail.cs @@ -0,0 +1,39 @@ +using EggLink.DanhengServer.Internationalization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Command.Command.Cmd +{ + [CommandInfo("mail", "Game.Command.Mail.Desc", "Game.Command.Mail.Usage", permission: "")] + public class CommandMail : ICommand + { + [CommandDefault] + public void Mail(CommandArg arg) + { + if (arg.Target == null) + { + arg.SendMsg(I18nManager.Translate("Game.Command.Notice.PlayerNotFound")); + return; + } + + if (arg.Args.Count < 5) + { + arg.SendMsg(I18nManager.Translate("Game.Command.Notice.InvalidArguments")); + return; + } + + var sender = arg.Args[0]; + var title = arg.Args[1]; + var content = arg.Args[2]; + var templateId = int.Parse(arg.Args[3]); + var expiredDay = int.Parse(arg.Args[4]); + + arg.Target.Player!.MailManager!.SendMail(sender, title, content, templateId, expiredDay); + + arg.SendMsg(I18nManager.Translate("Game.Command.Mail.MailSent")); + } + } +} diff --git a/GameServer/Command/Cmd/CommandMission.cs b/Command/Command/Cmd/CommandMission.cs similarity index 100% rename from GameServer/Command/Cmd/CommandMission.cs rename to Command/Command/Cmd/CommandMission.cs diff --git a/GameServer/Command/Cmd/CommandRelic.cs b/Command/Command/Cmd/CommandRelic.cs similarity index 94% rename from GameServer/Command/Cmd/CommandRelic.cs rename to Command/Command/Cmd/CommandRelic.cs index 7d34ce77..2caa124b 100644 --- a/GameServer/Command/Cmd/CommandRelic.cs +++ b/Command/Command/Cmd/CommandRelic.cs @@ -40,7 +40,8 @@ namespace EggLink.DanhengServer.Command.Cmd } GameData.RelicConfigData.TryGetValue(int.Parse(arg.BasicArgs[0]), out var itemConfig); - if (itemConfig == null) + GameData.ItemConfigData.TryGetValue(int.Parse(arg.BasicArgs[0]), out var itemConfigExcel); + if (itemConfig == null || itemConfigExcel == null) { arg.SendMsg(I18nManager.Translate("Game.Command.Relic.RelicNotFound")); return; @@ -141,7 +142,7 @@ namespace EggLink.DanhengServer.Command.Cmd player.InventoryManager!.AddItem(itemData); } - arg.SendMsg(I18nManager.Translate("Game.Command.Relic.RelicGiven", player.Uid.ToString(), amount.ToString(), itemData.ItemId.ToString(), itemData.MainAffix.ToString())); + arg.SendMsg(I18nManager.Translate("Game.Command.Relic.RelicGiven", player.Uid.ToString(), amount.ToString(), itemConfigExcel.Name ?? itemData.ItemId.ToString(), itemData.MainAffix.ToString())); } } } diff --git a/GameServer/Command/Cmd/CommandReload.cs b/Command/Command/Cmd/CommandReload.cs similarity index 100% rename from GameServer/Command/Cmd/CommandReload.cs rename to Command/Command/Cmd/CommandReload.cs diff --git a/GameServer/Command/Cmd/CommandRogue.cs b/Command/Command/Cmd/CommandRogue.cs similarity index 100% rename from GameServer/Command/Cmd/CommandRogue.cs rename to Command/Command/Cmd/CommandRogue.cs diff --git a/GameServer/Command/Cmd/CommandScene.cs b/Command/Command/Cmd/CommandScene.cs similarity index 100% rename from GameServer/Command/Cmd/CommandScene.cs rename to Command/Command/Cmd/CommandScene.cs diff --git a/GameServer/Command/Cmd/CommandUnlockAll.cs b/Command/Command/Cmd/CommandUnlockAll.cs similarity index 100% rename from GameServer/Command/Cmd/CommandUnlockAll.cs rename to Command/Command/Cmd/CommandUnlockAll.cs diff --git a/GameServer/Command/CommandArg.cs b/Command/Command/CommandArg.cs similarity index 93% rename from GameServer/Command/CommandArg.cs rename to Command/Command/CommandArg.cs index aefaac85..88c984c3 100644 --- a/GameServer/Command/CommandArg.cs +++ b/Command/Command/CommandArg.cs @@ -11,6 +11,7 @@ namespace EggLink.DanhengServer.Command public class CommandArg { public string Raw { get; } + public List Args { get; } = []; public List BasicArgs { get; } = []; public Dictionary CharacterArgs { get; } = []; public Connection? Target { get; set; } @@ -33,14 +34,17 @@ namespace EggLink.DanhengServer.Command try { CharacterArgs.Add(arg[..1], arg[1..]); + Args.Add(arg); } catch { BasicArgs.Add(arg); + Args.Add(arg); } } else { BasicArgs.Add(arg); + Args.Add(arg); } } if (con != null) diff --git a/GameServer/Command/CommandManager.cs b/Command/Command/CommandManager.cs similarity index 93% rename from GameServer/Command/CommandManager.cs rename to Command/Command/CommandManager.cs index c738dffe..ba2b5ab3 100644 --- a/GameServer/Command/CommandManager.cs +++ b/Command/Command/CommandManager.cs @@ -1,6 +1,6 @@ using EggLink.DanhengServer.Database; +using EggLink.DanhengServer.GameServer.Command; using EggLink.DanhengServer.Internationalization; -using EggLink.DanhengServer.Program; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Server; using EggLink.DanhengServer.Util; @@ -16,6 +16,7 @@ namespace EggLink.DanhengServer.Command { public class CommandManager { + public static CommandManager? Instance { get; private set; } public Dictionary Commands { get; } = []; public Dictionary CommandInfo { get; } = []; public Logger Logger { get; } = new Logger("CommandManager"); @@ -23,6 +24,7 @@ namespace EggLink.DanhengServer.Command public void RegisterCommand() { + Instance = this; foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) { var attr = type.GetCustomAttribute(); @@ -84,10 +86,15 @@ namespace EggLink.DanhengServer.Command } return; } - } else if (sender is PlayerCommandSender player) + } else { // player - tempTarget = player.Player.Connection; + tempTarget = Listener.GetActiveConnection(sender.GetSender()); + if (tempTarget == null) + { + sender.SendMsg(I18nManager.Translate("Game.Command.Notice.TargetNotFound", sender.GetSender().ToString())); + return; + } } if (tempTarget != null && !tempTarget.IsOnline) diff --git a/GameServer/Command/CommandInfo.cs b/Common/Command/CommandInfo.cs similarity index 100% rename from GameServer/Command/CommandInfo.cs rename to Common/Command/CommandInfo.cs diff --git a/Common/Command/CommandSender.cs b/Common/Command/CommandSender.cs new file mode 100644 index 00000000..38631aad --- /dev/null +++ b/Common/Command/CommandSender.cs @@ -0,0 +1,37 @@ +using EggLink.DanhengServer.Database; +using EggLink.DanhengServer.Database.Account; +using EggLink.DanhengServer.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Command +{ + public interface ICommandSender + { + public void SendMsg(string msg); + + public bool HasPermission(string permission); + + public int GetSender(); + } + + public class ConsoleCommandSender(Logger logger) : ICommandSender + { + public void SendMsg(string msg) + { + logger.Info(msg); + } + + public bool HasPermission(string permission) + { + return true; + } + public int GetSender() + { + return 0; + } + } +} diff --git a/GameServer/Command/ICommand.cs b/Common/Command/ICommand.cs similarity index 100% rename from GameServer/Command/ICommand.cs rename to Common/Command/ICommand.cs diff --git a/Common/Common.csproj b/Common/Common.csproj index 377b9ef3..4766b2b4 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -5,6 +5,7 @@ enable enable EggLink.DanhengServer + DanhengCommon diff --git a/Common/Configuration/ConfigContainer.cs b/Common/Configuration/ConfigContainer.cs index 3c12005e..1d42af7d 100644 --- a/Common/Configuration/ConfigContainer.cs +++ b/Common/Configuration/ConfigContainer.cs @@ -11,6 +11,7 @@ namespace EggLink.DanhengServer.Configuration public DatabaseConfig Database { get; set; } = new DatabaseConfig(); public ServerOption ServerOption { get; set; } = new ServerOption(); public DownloadUrlConfig DownloadUrl { get; set; } = new DownloadUrlConfig(); + public MuipServerConfig MuipServer { get; set; } = new MuipServerConfig(); } public class HttpServerConfig @@ -47,6 +48,7 @@ namespace EggLink.DanhengServer.Configuration public string DatabasePath { get; set; } = "Config/Database"; public string LogPath { get; set; } = "Logs"; public string PluginPath { get; set; } = "Plugins"; + public string PluginConfigPath { get; set; } = "Plugins/Config"; } public class DatabaseConfig @@ -98,4 +100,9 @@ namespace EggLink.DanhengServer.Configuration public string? LuaUrl { get; set; } = null; public string? IfixUrl { get; set; } = null; } + + public class MuipServerConfig + { + public string AdminKey { get; set; } = ""; + } } diff --git a/Common/Data/Excel/RogueNPCDialogueExcel.cs b/Common/Data/Excel/RogueNPCDialogueExcel.cs index b1cbde7d..1a9ad473 100644 --- a/Common/Data/Excel/RogueNPCDialogueExcel.cs +++ b/Common/Data/Excel/RogueNPCDialogueExcel.cs @@ -29,10 +29,10 @@ namespace EggLink.DanhengServer.Data.Excel GameData.RogueNPCDialogueData.Add(GetId(), this); } - public bool CanUseInCommon() + public bool CanUseInVer(int version) { GameData.RogueHandBookEventData.TryGetValue(HandbookEventID, out var handbookEvent); - return DialogueInfo != null && handbookEvent != null && handbookEvent.EventTypeList.Contains(100); + return DialogueInfo != null && handbookEvent != null && handbookEvent.EventTypeList.Contains(version); } } } diff --git a/Common/Database/Mail/MailData.cs b/Common/Database/Mail/MailData.cs new file mode 100644 index 00000000..aa7c164e --- /dev/null +++ b/Common/Database/Mail/MailData.cs @@ -0,0 +1,62 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Proto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Database.Mail +{ + public class MailData : BaseDatabaseDataHelper + { + public List MailList { get; set; } = []; + public List NoticeMailList { get; set; } = []; + + public int NextMailId { get; set; } = 1; + } + + public class MailInfo + { + public int MailID { get; set; } + public string SenderName { get; set; } = ""; + public string Title { get; set; } = ""; + public string Content { get; set; } = ""; + public bool IsRead { get; set; } + public bool IsStar { get; set; } + public long SendTime { get; set; } + public long ExpireTime { get; set; } + public int TemplateID { get; set; } + public MailAttachmentInfo Attachment { get; set; } = new(); + + public ClientMail ToProto() + { + return new() + { + Id = (uint)MailID, + Sender = SenderName, + Content = Content, + MailType = IsStar ? MailType.Star : MailType.Normal, + ExpireTime = ExpireTime, + IsRead = IsRead, + TemplateId = (uint)TemplateID, + Title = Title, + Time = SendTime, + Attachment = Attachment.ToProto() + }; + } + } + + public class MailAttachmentInfo + { + public List Items { get; set; } = []; + + public ItemList ToProto() + { + return new() + { + ItemList_ = { Items.Select(x => x.ToProto()).ToList() } + }; + } + } +} diff --git a/Common/Internationalization/I18nManager.cs b/Common/Internationalization/I18nManager.cs index fc2aaf0d..dd01e44b 100644 --- a/Common/Internationalization/I18nManager.cs +++ b/Common/Internationalization/I18nManager.cs @@ -21,7 +21,9 @@ namespace EggLink.DanhengServer.Internationalization var languageType = Type.GetType(languageStr); if (languageType == null) { - throw new Exception("Language not found"); + Logger.Error("Language not found, fallback to EN"); + // fallback to English + languageType = Type.GetType("EggLink.DanhengServer.Internationalization.Message.LanguageEN")!; } var language = Activator.CreateInstance(languageType) ?? throw new Exception("Language not found"); Language = language; diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 29b429f4..ddb26da5 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -79,6 +79,7 @@ namespace EggLink.DanhengServer.Internationalization.Message public RogueTextCHS Rogue { get; } = new(); public SceneTextCHS Scene { get; } = new(); public UnlockAllTextCHS UnlockAll { get; } = new(); + public MailTextCHS Mail { get; } = new(); } #endregion @@ -282,6 +283,17 @@ namespace EggLink.DanhengServer.Internationalization.Message public string SceneReloaded { get; } = "场景已重新加载!"; } + /// + /// path: Game.Command.Mail + /// + public class MailTextCHS + { + public string Desc { get; } = "管理玩家的邮件"; + public string Usage { get; } = "/mail /"; + public string MailSent { get; } = "邮件已发送!"; + public string MailSentWithAttachment { get; } = "带附件的邮件已发送!"; + } + #endregion #endregion diff --git a/Common/Util/ConfigManager.cs b/Common/Util/ConfigManager.cs index f7413242..651a2ebd 100644 --- a/Common/Util/ConfigManager.cs +++ b/Common/Util/ConfigManager.cs @@ -14,6 +14,8 @@ namespace EggLink.DanhengServer.Util { logger.Warn("Config file not found, creating a new one"); Config = new ConfigContainer(); + Config.MuipServer.AdminKey = Guid.NewGuid().ToString(); + logger.Info("Muipserver Admin key: " + Config.MuipServer.AdminKey); SaveConfig(); } using (var reader = new StreamReader(file.OpenRead())) diff --git a/DanhengServer.sln b/DanhengServer.sln index 66f9e59b..aa3594d8 100644 --- a/DanhengServer.sln +++ b/DanhengServer.sln @@ -19,6 +19,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command", "Command\Command.csproj", "{B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Program", "Program\Program.csproj", "{71D8488F-CAED-48EE-BD5C-F325FBAB991F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,6 +41,14 @@ Global {8E3A0EA5-F4BC-4478-AEB9-CAAC07F10BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E3A0EA5-F4BC-4478-AEB9-CAAC07F10BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E3A0EA5-F4BC-4478-AEB9-CAAC07F10BD3}.Release|Any CPU.Build.0 = Release|Any CPU + {B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B52B8DE8-E63E-4CE4-A75F-C17A97C86D89}.Release|Any CPU.Build.0 = Release|Any CPU + {71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71D8488F-CAED-48EE-BD5C-F325FBAB991F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GameServer/Command/CommandExecutor.cs b/GameServer/Command/CommandExecutor.cs new file mode 100644 index 00000000..f731e818 --- /dev/null +++ b/GameServer/Command/CommandExecutor.cs @@ -0,0 +1,20 @@ +using EggLink.DanhengServer.Command; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Command +{ + public static class CommandExecutor + { + public delegate void RunCommand(ICommandSender sender, string cmd); + public static event RunCommand? OnRunCommand; + + public static void ExecuteCommand(ICommandSender sender, string cmd) + { + OnRunCommand?.Invoke(sender, cmd); + } + } +} diff --git a/GameServer/Command/CommandSender.cs b/GameServer/Command/PlayerCommandSender.cs similarity index 65% rename from GameServer/Command/CommandSender.cs rename to GameServer/Command/PlayerCommandSender.cs index ea951724..1902f655 100644 --- a/GameServer/Command/CommandSender.cs +++ b/GameServer/Command/PlayerCommandSender.cs @@ -1,5 +1,6 @@ -using EggLink.DanhengServer.Database; +using EggLink.DanhengServer.Command; using EggLink.DanhengServer.Database.Account; +using EggLink.DanhengServer.Database; using EggLink.DanhengServer.Game.Player; using EggLink.DanhengServer.Server.Packet.Send.Friend; using EggLink.DanhengServer.Util; @@ -9,28 +10,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace EggLink.DanhengServer.Command +namespace EggLink.DanhengServer.GameServer.Command { - public interface ICommandSender - { - public void SendMsg(string msg); - - public bool HasPermission(string permission); - } - - public class ConsoleCommandSender(Logger logger) : ICommandSender - { - public void SendMsg(string msg) - { - logger.Info(msg); - } - - public bool HasPermission(string permission) - { - return true; - } - } - public class PlayerCommandSender(PlayerInstance player) : ICommandSender { public PlayerInstance Player = player; @@ -45,5 +26,10 @@ namespace EggLink.DanhengServer.Command var account = DatabaseHelper.Instance!.GetInstance(Player.Uid)!; return account.Permissions!.Contains(permission); } + + public int GetSender() + { + return Player.Uid; + } } } diff --git a/GameServer/Game/Challenge/ChallengeInstance.cs b/GameServer/Game/Challenge/ChallengeInstance.cs index e2b46dd3..56b7fc38 100644 --- a/GameServer/Game/Challenge/ChallengeInstance.cs +++ b/GameServer/Game/Challenge/ChallengeInstance.cs @@ -330,7 +330,7 @@ namespace EggLink.DanhengServer.Game.Challenge if (StoryBuffs != null && StoryBuffs.Count >= CurrentStage) { - proto.PlayerInfo.CurStoryBuff.BuffList.Add((uint)StoryBuffs[CurrentStage - 1]); + proto.PlayerInfo.CurStoryBuff.BuffList.Add(StoryBuffs.Select(x => (uint)x)); } // Early implementation for 2.3 diff --git a/GameServer/Game/Challenge/ChallengeManager.cs b/GameServer/Game/Challenge/ChallengeManager.cs index 7cee0ae7..cd258e44 100644 --- a/GameServer/Game/Challenge/ChallengeManager.cs +++ b/GameServer/Game/Challenge/ChallengeManager.cs @@ -24,12 +24,12 @@ namespace EggLink.DanhengServer.Game.Challenge public void StartChallenge(int challengeId, StartChallengeStoryBuffInfo? storyBuffs) { // Get challenge excel - if (!GameData.ChallengeConfigData.ContainsKey(challengeId)) + if (!GameData.ChallengeConfigData.TryGetValue(challengeId, out ChallengeConfigExcel? value)) { Player.SendPacket(new PacketStartChallengeScRsp((uint)Retcode.RetChallengeNotExist)); return; } - ChallengeConfigExcel Excel = GameData.ChallengeConfigData[challengeId]; + ChallengeConfigExcel Excel = value; // Sanity check lineups if (Excel.StageNum > 0) @@ -79,7 +79,7 @@ namespace EggLink.DanhengServer.Game.Challenge } // Set challenge data for player - ChallengeInstance instance = new ChallengeInstance(Player, Excel); + ChallengeInstance instance = new(Player, Excel); ChallengeInstance = instance; // Set first lineup before we enter scenes @@ -88,7 +88,7 @@ namespace EggLink.DanhengServer.Game.Challenge // Enter scene try { - Player.EnterScene(Excel.MapEntranceID, 0, true); + Player.EnterScene(Excel.MapEntranceID, 0, false); } catch { @@ -257,9 +257,9 @@ namespace EggLink.DanhengServer.Game.Challenge if (ChallengeData.Instance != null && ChallengeData.Instance.ChallengeId != 0) { int ChallengeId = ChallengeData.Instance.ChallengeId; - if (GameData.ChallengeConfigData.ContainsKey(ChallengeId)) + if (GameData.ChallengeConfigData.TryGetValue(ChallengeId, out ChallengeConfigExcel? value)) { - ChallengeConfigExcel Excel = GameData.ChallengeConfigData[ChallengeId]; + ChallengeConfigExcel Excel = value; ChallengeInstance instance = new ChallengeInstance(Player, Excel, ChallengeData.Instance); ChallengeInstance = instance; } diff --git a/GameServer/Game/ChessRogue/ChessRogueInstance.cs b/GameServer/Game/ChessRogue/ChessRogueInstance.cs index 0c082584..8fc7721e 100644 --- a/GameServer/Game/ChessRogue/ChessRogueInstance.cs +++ b/GameServer/Game/ChessRogue/ChessRogueInstance.cs @@ -48,6 +48,14 @@ namespace EggLink.DanhengServer.Game.ChessRogue CurLayer = Layers.First(); EventManager = new(player, this); + if (rogueVersionId == 202) + { + RogueType = 160; + } else + { + RogueType = 130; + } + foreach (var difficulty in areaExcel.DifficultyID) { if (GameData.RogueDLCDifficultyData.TryGetValue(difficulty, out var diff)) diff --git a/GameServer/Game/Friend/FriendManager.cs b/GameServer/Game/Friend/FriendManager.cs index 54c2da1f..779afacc 100644 --- a/GameServer/Game/Friend/FriendManager.cs +++ b/GameServer/Game/Friend/FriendManager.cs @@ -4,7 +4,7 @@ using EggLink.DanhengServer.Database.Friend; using EggLink.DanhengServer.Database.Inventory; using EggLink.DanhengServer.Database.Player; using EggLink.DanhengServer.Game.Player; -using EggLink.DanhengServer.Program; +using EggLink.DanhengServer.GameServer.Command; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Server; using EggLink.DanhengServer.Server.Packet.Send.Friend; @@ -162,7 +162,7 @@ namespace EggLink.DanhengServer.Game.Friend if (message?.StartsWith('/') == true) { var cmd = message[1..]; - EntryPoint.CommandManager.HandleCommand(cmd, new PlayerCommandSender(Player)); + CommandExecutor.ExecuteCommand(new PlayerCommandSender(Player), cmd); } } diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index f0955ed7..ef3b7520 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -38,7 +38,7 @@ namespace EggLink.DanhengServer.Game.Inventory var syncItems = new List(); foreach (var item in items) { - var i = AddItem(item.ItemId, items.Count, false, sync:false, returnRaw:true); + var i = AddItem(item.ItemId, item.Count, false, sync:false, returnRaw:true); if (i != null) { syncItems.Add(i); diff --git a/GameServer/Game/Mail/MailManager.cs b/GameServer/Game/Mail/MailManager.cs new file mode 100644 index 00000000..fc823325 --- /dev/null +++ b/GameServer/Game/Mail/MailManager.cs @@ -0,0 +1,96 @@ +using EggLink.DanhengServer.Database; +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Database.Mail; +using EggLink.DanhengServer.Game; +using EggLink.DanhengServer.Game.Player; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Mail; +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mail; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Game.Mail +{ + public class MailManager(PlayerInstance player) : BasePlayerManager(player) + { + public MailData MailData { get; private set; } = DatabaseHelper.Instance!.GetInstanceOrCreateNew(player.Uid); + + public List GetMailList() + { + return MailData.MailList; + } + + public MailInfo? GetMail(int mailId) + { + return MailData.MailList.Find(x => x.MailID == mailId); + } + + public void SendMail(string sender, string title, string content, int templateId, int expiredDay = 30) + { + var mail = new MailInfo() + { + MailID = MailData.NextMailId++, + SenderName = sender, + Content = content, + Title = title, + TemplateID = templateId, + SendTime = DateTime.Now.ToUnixSec(), + ExpireTime = DateTime.Now.AddDays(expiredDay).ToUnixSec(), + }; + + MailData.MailList.Add(mail); + + Player.SendPacket(new PacketNewMailScNotify(mail.MailID)); + } + + public void SendMail(string sender, string title, string content, int templateId, List attachments, int expiredDay = 30) + { + var mail = new MailInfo() + { + MailID = MailData.NextMailId++, + SenderName = sender, + Content = content, + Title = title, + TemplateID = templateId, + SendTime = DateTime.Now.ToUnixSec(), + ExpireTime = DateTime.Now.AddDays(expiredDay).ToUnixSec(), + Attachment = new() + { + Items = attachments + } + }; + + MailData.MailList.Add(mail); + + Player.SendPacket(new PacketNewMailScNotify(mail.MailID)); + } + + public List ToMailProto() + { + var list = new List(); + + foreach (var mail in MailData.MailList) + { + list.Add(mail.ToProto()); + } + + return list; + } + + public List ToNoticeMailProto() + { + var list = new List(); + + foreach (var mail in MailData.NoticeMailList) + { + list.Add(mail.ToProto()); + } + + return list; + } + } +} diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index cec7e578..3165c7bd 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -35,6 +35,7 @@ using EggLink.DanhengServer.Game.Drop; using static EggLink.DanhengServer.Plugin.Event.PluginEvent; using EggLink.DanhengServer.Plugin.Event; using EggLink.DanhengServer.Game.Task; +using EggLink.DanhengServer.GameServer.Game.Mail; namespace EggLink.DanhengServer.Game.Player { @@ -51,6 +52,7 @@ namespace EggLink.DanhengServer.Game.Player public MissionManager? MissionManager { get; private set; } public GachaManager? GachaManager { get; private set; } public MessageManager? MessageManager { get; private set; } + public MailManager? MailManager { get; private set; } public FriendManager? FriendManager { get; private set; } public RogueManager? RogueManager { get; private set; } @@ -124,6 +126,7 @@ namespace EggLink.DanhengServer.Game.Player MissionManager = new(this); GachaManager = new(this); MessageManager = new(this); + MailManager = new(this); FriendManager = new(this); RogueManager = new(this); ShopService = new(this); @@ -508,6 +511,10 @@ namespace EggLink.DanhengServer.Game.Player { EnterScene(OldEntryId > 0 ? OldEntryId : 2000101, 0, sendPacket); return; + } else if (plane.PlaneType == PlaneTypeEnum.Challenge && ChallengeManager!.ChallengeInstance == null) + { + EnterScene(100000103, 0, sendPacket); + return; } // TODO: Sanify check diff --git a/GameServer/Game/Rogue/BaseRogueInstance.cs b/GameServer/Game/Rogue/BaseRogueInstance.cs index 9770828b..600a012d 100644 --- a/GameServer/Game/Rogue/BaseRogueInstance.cs +++ b/GameServer/Game/Rogue/BaseRogueInstance.cs @@ -19,6 +19,7 @@ namespace EggLink.DanhengServer.Game.Rogue public PlayerInstance Player { get; set; } = player; public Database.Lineup.LineupInfo? CurLineup { get; set; } public int RogueVersionId { get; set; } = rogueVersionId; + public int RogueType { get; set; } = 100; public int CurReviveCost { get; set; } = 80; public int CurRerollCost { get; set; } = 30; public int BaseRerollCount { get; set; } = 1; @@ -406,7 +407,7 @@ namespace EggLink.DanhengServer.Game.Rogue do { dialogue = GameData.RogueNPCDialogueData.Values.ToList().RandomElement(); - } while (dialogue == null || !dialogue.CanUseInCommon()); + } while (dialogue == null || !dialogue.CanUseInVer(RogueType)); var instance = new RogueEventInstance(dialogue, npc, CurEventUniqueID++); EventManager?.AddEvent(instance); diff --git a/GameServer/Game/Rogue/Event/RogueEventInstance.cs b/GameServer/Game/Rogue/Event/RogueEventInstance.cs index 7b26bde5..add5e52c 100644 --- a/GameServer/Game/Rogue/Event/RogueEventInstance.cs +++ b/GameServer/Game/Rogue/Event/RogueEventInstance.cs @@ -51,7 +51,7 @@ namespace EggLink.DanhengServer.Game.Rogue.Event var proto = new DialogueEvent() { EventId = (uint)EventId, - GameModeType = (uint)EventEntity.Scene.Excel.PlaneType, + GameModeType = (uint)(EventEntity.Scene.CustomGameModeId > 0 ? EventEntity.Scene.CustomGameModeId : (int)EventEntity.Scene.Excel.PlaneType), EventUniqueId = (uint)EventUniqueId, }; diff --git a/GameServer/Game/Scene/SceneInstance.cs b/GameServer/Game/Scene/SceneInstance.cs index 7e95c4a9..17248949 100644 --- a/GameServer/Game/Scene/SceneInstance.cs +++ b/GameServer/Game/Scene/SceneInstance.cs @@ -61,7 +61,7 @@ namespace EggLink.DanhengServer.Game.Scene if (Player.ChessRogueManager!.RogueInstance != null) { EntityLoader = new ChessRogueEntityLoader(this); - CustomGameModeId = 16; + CustomGameModeId = 16; // ChessRogue } else { EntityLoader = new RogueEntityLoader(this, Player); diff --git a/GameServer/GameServer.csproj b/GameServer/GameServer.csproj index 4dc7898e..af4ae494 100644 --- a/GameServer/GameServer.csproj +++ b/GameServer/GameServer.csproj @@ -1,14 +1,14 @@  - Exe + Library net8.0 enable enable - EggLink.DanhengServer - EggLink.DanhengServer.Program.EntryPoint + EggLink.DanhengServer.GameServer + true - DanhengServer + DanhengGameServer @@ -17,12 +17,12 @@ - - + + diff --git a/GameServer/Plugin/Constructor/IPlugin.cs b/GameServer/Plugin/Constructor/IPlugin.cs index 76c927ef..1e3a37ca 100644 --- a/GameServer/Plugin/Constructor/IPlugin.cs +++ b/GameServer/Plugin/Constructor/IPlugin.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; namespace EggLink.DanhengServer.Plugin.Constructor { + [Obsolete("以俟君子 Wait for someone to develop it")] public interface IPlugin { public void OnLoad(); diff --git a/GameServer/Plugin/Constructor/PluginInfo.cs b/GameServer/Plugin/Constructor/PluginInfo.cs new file mode 100644 index 00000000..19a7c632 --- /dev/null +++ b/GameServer/Plugin/Constructor/PluginInfo.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Plugin.Constructor +{ + [AttributeUsage(AttributeTargets.Class)] + public class PluginInfo(string name, string description, string version) : Attribute + { + public string Name { get; } = name; + public string Description { get; } = description; + public string Version { get; } = version; + } +} diff --git a/GameServer/Plugin/PluginManager.cs b/GameServer/Plugin/PluginManager.cs index 66ba4c90..980e304c 100644 --- a/GameServer/Plugin/PluginManager.cs +++ b/GameServer/Plugin/PluginManager.cs @@ -1,7 +1,10 @@ using EggLink.DanhengServer.Plugin.Constructor; using EggLink.DanhengServer.Util; +using McMaster.NETCore.Plugins; +using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -12,10 +15,12 @@ namespace EggLink.DanhengServer.Plugin public class PluginManager { private readonly static Logger logger = new("PluginManager"); - public readonly static List Plugins = []; + public readonly static Dictionary Plugins = []; public readonly static Dictionary> PluginAssemblies = []; + #region Plugin Manager + public static void LoadPlugins() { // get all the plugins in the plugin directory @@ -24,43 +29,81 @@ namespace EggLink.DanhengServer.Plugin Directory.CreateDirectory(ConfigManager.Config.Path.PluginPath); } + if (!Directory.Exists(ConfigManager.Config.Path.PluginConfigPath)) + { + Directory.CreateDirectory(ConfigManager.Config.Path.PluginConfigPath); + } + var plugins = Directory.GetFiles(ConfigManager.Config.Path.PluginPath, "*.dll"); + var loaders = new List(); + AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + { + var assemblyName = new AssemblyName(args.Name).Name + ".dll"; + var assemblyPath = Path.Combine(ConfigManager.Config.Path.PluginPath, assemblyName); + + if (File.Exists(assemblyPath)) + { + return Assembly.LoadFrom(assemblyPath); + } + + return null; + }; foreach (var plugin in plugins) { var fileInfo = new FileInfo(plugin); LoadPlugin(fileInfo.FullName); } - - logger.Info($"Loaded {Plugins.Count} plugins"); } public static void LoadPlugin(string plugin) { try { - var assembly = Assembly.LoadFile(plugin); + var config = new PluginConfig(plugin) + { + PreferSharedTypes = true, + LoadInMemory = true, + }; + + var loader = new PluginLoader(config); + + var assembly = loader.LoadDefaultAssembly(); var types = assembly.GetTypes(); - bool isPlugin = false; foreach (var type in types) { - if (type.GetInterface("IPlugin") != null) + if (typeof(IPlugin).IsAssignableFrom(type)) { if (Activator.CreateInstance(type) is IPlugin pluginInstance) { - pluginInstance.OnLoad(); - Plugins.Add(pluginInstance); - - if (!PluginAssemblies.TryGetValue(pluginInstance, out List? value)) + var pluginInfo = type.GetCustomAttribute(); + if (pluginInfo != null) { - value = new List(); - PluginAssemblies[pluginInstance] = value; + logger.Info($"Loaded plugin {pluginInfo.Name} v{pluginInfo.Version}: {pluginInfo.Description}"); + } + else + { + logger.Info($"Loaded plugin {plugin}: No plugin info"); + continue; } - value.AddRange(types); + if (Plugins.Values.Any(p => p.Name == pluginInfo.Name)) + { + logger.Error($"Failed to load plugin {plugin}: Plugin already loaded"); + continue; + } - isPlugin = true; - break; + Plugins.Add(pluginInstance, pluginInfo); + + if (!PluginAssemblies.TryGetValue(pluginInstance, out var pluginTypes)) + { + pluginTypes = []; + PluginAssemblies[pluginInstance] = pluginTypes; + } + + pluginTypes.AddRange(types); + + pluginInstance.OnLoad(); } else { @@ -68,33 +111,41 @@ namespace EggLink.DanhengServer.Plugin } } } - - if (!isPlugin) - { - logger.Error($"Failed to load plugin {plugin}: Plugin does not implement IPlugin"); - } } - catch (Exception e) + catch (Exception ex) { - logger.Error($"Failed to load plugin {plugin}: {e.Message}"); + logger.Error($"Failed to load plugin {plugin}: {ex.Message}"); } } + public static void UnloadPlugin(IPlugin plugin) + { + if (Plugins.TryGetValue(plugin, out PluginInfo? value)) + { + plugin.OnUnload(); + Plugins.Remove(plugin); + PluginAssemblies.Remove(plugin); + logger.Info($"Unloaded plugin {value.Name}"); + } + } + + public static void UnloadPlugins() { - foreach (var plugin in Plugins) + foreach (var plugin in Plugins.Keys) { - plugin.OnUnload(); + UnloadPlugin(plugin); } logger.Info("Unloaded all plugins"); - Plugins.Clear(); } + #endregion + public static List GetPluginAssemblies() { var assemblies = new List(); - foreach (var plugin in Plugins) + foreach (var plugin in Plugins.Keys) { if (PluginAssemblies.TryGetValue(plugin, out List? value)) { diff --git a/GameServer/Server/Connection.cs b/GameServer/Server/Connection.cs index 0aa5bfe5..646608a1 100644 --- a/GameServer/Server/Connection.cs +++ b/GameServer/Server/Connection.cs @@ -5,7 +5,6 @@ using System.Reflection; using EggLink.DanhengServer.Common.Enums; using EggLink.DanhengServer.Game.Player; using EggLink.DanhengServer.KcpSharp; -using EggLink.DanhengServer.Program; using EggLink.DanhengServer.Server.Packet; using EggLink.DanhengServer.Util; using Google.Protobuf; @@ -70,7 +69,7 @@ public partial class Connection } #pragma warning disable CS8600 Type? typ = AppDomain.CurrentDomain.GetAssemblies(). - SingleOrDefault(assembly => assembly.GetName().Name == "Common")!.GetTypes().First(t => t.Name == $"{LogMap[opcode.ToString()]}"); //get the type using the packet name + SingleOrDefault(assembly => assembly.GetName().Name == "DanhengCommon")!.GetTypes().First(t => t.Name == $"{LogMap[opcode.ToString()]}"); //get the type using the packet name MessageDescriptor? descriptor = (MessageDescriptor)typ.GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static)!.GetValue(null, null); // get the static property Descriptor IMessage? packet = descriptor!.Parser.ParseFrom(payload); #pragma warning restore CS8600 @@ -178,7 +177,7 @@ public partial class Connection private bool HandlePacket(ushort opcode, byte[] header, byte[] payload) { // Find the Handler for this opcode - Handler? handler = EntryPoint.HandlerManager.GetHandler(opcode); + Handler? handler = HandlerManager.GetHandler(opcode); if (handler != null) { // Handle diff --git a/GameServer/Server/Packet/HandlerManager.cs b/GameServer/Server/Packet/HandlerManager.cs index 6f7e9740..eda16bcf 100644 --- a/GameServer/Server/Packet/HandlerManager.cs +++ b/GameServer/Server/Packet/HandlerManager.cs @@ -2,11 +2,11 @@ namespace EggLink.DanhengServer.Server.Packet { - public class HandlerManager + public static class HandlerManager { - public Dictionary handlers = []; + public static Dictionary handlers = []; - public HandlerManager() + public static void Init() { var classes = Assembly.GetExecutingAssembly().GetTypes(); // Get all classes in the assembly foreach (var cls in classes) @@ -20,7 +20,7 @@ namespace EggLink.DanhengServer.Server.Packet } } - public Handler? GetHandler(int cmdId) + public static Handler? GetHandler(int cmdId) { try { diff --git a/GameServer/Server/Packet/Recv/Friend/HandlerSendMsgCsReq.cs b/GameServer/Server/Packet/Recv/Friend/HandlerSendMsgCsReq.cs index afd1a236..48d95d81 100644 --- a/GameServer/Server/Packet/Recv/Friend/HandlerSendMsgCsReq.cs +++ b/GameServer/Server/Packet/Recv/Friend/HandlerSendMsgCsReq.cs @@ -1,5 +1,4 @@ using EggLink.DanhengServer.Command; -using EggLink.DanhengServer.Program; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Server.Packet.Send.Friend; using EggLink.DanhengServer.Server.Packet.Send.Gacha; diff --git a/GameServer/Server/Packet/Recv/Mail/HandlerGetMailCsReq.cs b/GameServer/Server/Packet/Recv/Mail/HandlerGetMailCsReq.cs new file mode 100644 index 00000000..150b72d2 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Mail/HandlerGetMailCsReq.cs @@ -0,0 +1,20 @@ +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Mail; +using EggLink.DanhengServer.Server; +using EggLink.DanhengServer.Server.Packet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Mail +{ + [Opcode(CmdIds.GetMailCsReq)] + public class HandlerGetMailCsReq : Handler + { + public override void OnHandle(Connection connection, byte[] header, byte[] data) + { + connection.SendPacket(new PacketGetMailScRsp(connection.Player!)); + } + } +} diff --git a/GameServer/Server/Packet/Recv/Mail/HandlerMarkReadMailCsReq.cs b/GameServer/Server/Packet/Recv/Mail/HandlerMarkReadMailCsReq.cs new file mode 100644 index 00000000..13f3e6bf --- /dev/null +++ b/GameServer/Server/Packet/Recv/Mail/HandlerMarkReadMailCsReq.cs @@ -0,0 +1,33 @@ +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Mail; +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Server; +using EggLink.DanhengServer.Server.Packet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Mail +{ + [Opcode(CmdIds.MarkReadMailCsReq)] + public class HandlerMarkReadMailCsReq : Handler + { + public override void OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = MarkReadMailCsReq.Parser.ParseFrom(data); + var player = connection.Player!; + + var mail = player.MailManager!.GetMail((int)req.Id); + + if (mail != null) + { + mail.IsRead = true; + connection.SendPacket(new PacketMarkReadMailScRsp(req.Id)); + } else + { + connection.SendPacket(new PacketMarkReadMailScRsp(Retcode.RetMailMailIdInvalid)); + } + } + } +} diff --git a/GameServer/Server/Packet/Send/Challenge/PacketStartChallengeScRsp.cs b/GameServer/Server/Packet/Send/Challenge/PacketStartChallengeScRsp.cs index 4f10bfe8..044d5dac 100644 --- a/GameServer/Server/Packet/Send/Challenge/PacketStartChallengeScRsp.cs +++ b/GameServer/Server/Packet/Send/Challenge/PacketStartChallengeScRsp.cs @@ -17,13 +17,15 @@ namespace EggLink.DanhengServer.Server.Packet.Send.Challenge public PacketStartChallengeScRsp(PlayerInstance player) : base(CmdIds.StartChallengeScRsp) { - StartChallengeScRsp proto = new StartChallengeScRsp() { }; + StartChallengeScRsp proto = new() + { + }; if (player.ChallengeManager!.ChallengeInstance != null) { proto.CurChallenge = player.ChallengeManager.ChallengeInstance.ToProto(); proto.Lineup = player.LineupManager!.GetExtraLineup(ExtraLineupType.LineupChallenge)!.ToProto(); // Deprecated in 2.3 - + proto.Scene = player.SceneInstance!.ToProto(); // Early implementation for 2.3 /* proto.LineupList.Add(player.LineupManager!.GetExtraLineup(ExtraLineupType.LineupChallenge)!.ToProto()); proto.Lineup.Add(player.LineupManager!.GetExtraLineup(ExtraLineupType.LineupChallenge2)!.ToProto()); */ diff --git a/GameServer/Server/Packet/Send/Mail/PacketGetMailScRsp.cs b/GameServer/Server/Packet/Send/Mail/PacketGetMailScRsp.cs new file mode 100644 index 00000000..f0494452 --- /dev/null +++ b/GameServer/Server/Packet/Send/Mail/PacketGetMailScRsp.cs @@ -0,0 +1,29 @@ +using EggLink.DanhengServer.Game.Player; +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Server.Packet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Mail +{ + public class PacketGetMailScRsp : BasePacket + { + public PacketGetMailScRsp(PlayerInstance player) : base(CmdIds.GetMailScRsp) + { + var list = player.MailManager!.ToMailProto(); + var noticeList = player.MailManager!.ToNoticeMailProto(); + var proto = new GetMailScRsp + { + IsEnd = true, + MailList = { list }, + NoticeMailList = { noticeList }, + TotalNum = (uint)(list.Count + noticeList.Count) + }; + + SetData(proto); + } + } +} diff --git a/GameServer/Server/Packet/Send/Mail/PacketMarkReadMailScRsp.cs b/GameServer/Server/Packet/Send/Mail/PacketMarkReadMailScRsp.cs new file mode 100644 index 00000000..60327b30 --- /dev/null +++ b/GameServer/Server/Packet/Send/Mail/PacketMarkReadMailScRsp.cs @@ -0,0 +1,33 @@ +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Server.Packet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Mail +{ + public class PacketMarkReadMailScRsp : BasePacket + { + public PacketMarkReadMailScRsp(uint mailId) : base(CmdIds.MarkReadMailScRsp) + { + var proto = new MarkReadMailScRsp() + { + Id = mailId + }; + + SetData(proto); + } + + public PacketMarkReadMailScRsp(Retcode retcode) : base(CmdIds.MarkReadMailScRsp) + { + var proto = new MarkReadMailScRsp() + { + Retcode = (uint)retcode + }; + + SetData(proto); + } + } +} diff --git a/GameServer/Server/Packet/Send/Mail/PacketNewMailScNotify.cs b/GameServer/Server/Packet/Send/Mail/PacketNewMailScNotify.cs new file mode 100644 index 00000000..9e55282f --- /dev/null +++ b/GameServer/Server/Packet/Send/Mail/PacketNewMailScNotify.cs @@ -0,0 +1,23 @@ +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Server.Packet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Mail +{ + public class PacketNewMailScNotify : BasePacket + { + public PacketNewMailScNotify(int id) : base(CmdIds.NewMailScNotify) + { + var proto = new NewMailScNotify() + { + MailIdList = { (uint)id } + }; + + SetData(proto); + } + } +} diff --git a/GameServer/Handbook/HandbookGenerator.cs b/Program/Handbook/HandbookGenerator.cs similarity index 100% rename from GameServer/Handbook/HandbookGenerator.cs rename to Program/Handbook/HandbookGenerator.cs diff --git a/Program/Program.csproj b/Program/Program.csproj new file mode 100644 index 00000000..ec844455 --- /dev/null +++ b/Program/Program.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + EggLink.DanhengServer.Program + DanhengServer + + + + + + + + + + diff --git a/GameServer/Program/EntryPoint.cs b/Program/Program/EntryPoint.cs similarity index 85% rename from GameServer/Program/EntryPoint.cs rename to Program/Program/EntryPoint.cs index 7e358e15..a3e1c526 100644 --- a/GameServer/Program/EntryPoint.cs +++ b/Program/Program/EntryPoint.cs @@ -4,13 +4,15 @@ using EggLink.DanhengServer.Configuration; using EggLink.DanhengServer.WebServer; using EggLink.DanhengServer.Database; using EggLink.DanhengServer.Server; -using EggLink.DanhengServer.Server.Packet; using Newtonsoft.Json.Linq; using Newtonsoft.Json; -using EggLink.DanhengServer.Command; using EggLink.DanhengServer.Handbook; using EggLink.DanhengServer.Internationalization; using EggLink.DanhengServer.Plugin; +using EggLink.DanhengServer.Command; +using EggLink.DanhengServer.Server.Packet; +using EggLink.DanhengServer.GameServer.Command; +using EggLink.DanhengServer.WebServer.Server; namespace EggLink.DanhengServer.Program { @@ -19,7 +21,6 @@ namespace EggLink.DanhengServer.Program private readonly static Logger logger = new("Program"); public readonly static DatabaseHelper DatabaseHelper = new(); public readonly static Listener Listener = new(); - public readonly static HandlerManager HandlerManager = new(); public readonly static CommandManager CommandManager = new(); public static void Main(string[] args) @@ -75,18 +76,6 @@ namespace EggLink.DanhengServer.Program return; } - // Load the plugins - logger.Info("Loading plugins..."); - try - { - PluginManager.LoadPlugins(); - } catch (Exception e) - { - logger.Error("Failed to load plugins", e); - Console.ReadLine(); - return; - } - // Load the game data logger.Info("Loading game data..."); try @@ -130,10 +119,32 @@ namespace EggLink.DanhengServer.Program Console.ReadLine(); return; } - + + // Load the plugins + logger.Info("Loading plugins..."); + try + { + PluginManager.LoadPlugins(); + } + catch (Exception e) + { + logger.Error("Failed to load plugins", e); + Console.ReadLine(); + return; + } + + CommandExecutor.OnRunCommand += (sender, e) => + { + CommandManager.HandleCommand(e, sender); + }; + + MuipManager.OnExecuteCommand += CommandManager.HandleCommand; + // generate the handbook HandbookGenerator.Generate(); + HandlerManager.Init(); + WebProgram.Main([], GetConfig().HttpServer.PublicPort, GetConfig().HttpServer.GetDisplayAddress()); logger.Info($"Dispatch Server is running on {GetConfig().HttpServer.GetDisplayAddress()}"); @@ -143,7 +154,7 @@ namespace EggLink.DanhengServer.Program logger.Info($"Done in {elapsed.TotalSeconds.ToString()[..4]}s! Type '/help' to get help of commands."); #if DEBUG - JsonConvert.DeserializeObject(File.ReadAllText("LogMap.json"))!.Properties().ToList().ForEach(x => Connection.LogMap.Add(x.Name, x.Value.ToString())); + GenerateLogMap(); #endif if (GetConfig().ServerOption.EnableMission) { @@ -165,5 +176,21 @@ namespace EggLink.DanhengServer.Program DatabaseHelper.SaveThread?.Interrupt(); DatabaseHelper.SaveDatabase(); } + +#if DEBUG + + private static void GenerateLogMap() + { + // get opcode from CmdIds + var opcodes = typeof(CmdIds).GetFields().Where(x => x.FieldType == typeof(int)).ToList(); + foreach (var opcode in opcodes) + { + var name = opcode.Name; + var value = (int)opcode.GetValue(null)!; + Connection.LogMap.Add(value.ToString(), name); + } + } + +#endif } } diff --git a/WebServer/Controllers/GateServerRoutes.cs b/WebServer/Controllers/GateServerRoutes.cs index a5adfb71..c12d88a5 100644 --- a/WebServer/Controllers/GateServerRoutes.cs +++ b/WebServer/Controllers/GateServerRoutes.cs @@ -1,4 +1,4 @@ -using EggLink.DanhengServer.Server.Http.Handler; +using EggLink.DanhengServer.WebServer.Handler; using Microsoft.AspNetCore.Mvc; namespace EggLink.DanhengServer.WebServer.Controllers diff --git a/WebServer/Controllers/MuipServerRoutes.cs b/WebServer/Controllers/MuipServerRoutes.cs new file mode 100644 index 00000000..034a170d --- /dev/null +++ b/WebServer/Controllers/MuipServerRoutes.cs @@ -0,0 +1,33 @@ +using EggLink.DanhengServer.Util; +using EggLink.DanhengServer.WebServer.Request; +using EggLink.DanhengServer.WebServer.Response; +using EggLink.DanhengServer.WebServer.Server; +using Microsoft.AspNetCore.Mvc; + +namespace EggLink.DanhengServer.WebServer.Controllers +{ + [ApiController] + [Route("/")] + public class MuipServerRoutes + { + [HttpGet("/muip/auth_admin")] + [HttpPost("/muip/auth_admin")] + public IActionResult AuthAdminKey([FromBody] AuthAdminKeyRequestBody req) + { + var data = MuipManager.AuthAdminAndCreateSession(req.admin_key, req.key_type); + if (data == null) + { + return new JsonResult(new AuthAdminKeyResponse(1, "Admin key is invalid!", null)); + } + return new JsonResult(new AuthAdminKeyResponse(0, "Authorized admin key successfully!", data)); + } + + [HttpGet("/muip/exec_cmd")] + [HttpPost("/muip/exec_cmd")] + public IActionResult ExecuteCommand([FromBody] AdminExecRequest req) + { + var resp = MuipManager.ExecuteCommand(req.SessionId, req.Command, req.TargetUid); + return new JsonResult(resp); + } + } +} diff --git a/WebServer/Handler/QueryGatewayHandler.cs b/WebServer/Handler/QueryGatewayHandler.cs index 5686e3be..24705b6b 100644 --- a/WebServer/Handler/QueryGatewayHandler.cs +++ b/WebServer/Handler/QueryGatewayHandler.cs @@ -2,7 +2,7 @@ using EggLink.DanhengServer.Util; using Google.Protobuf; -namespace EggLink.DanhengServer.Server.Http.Handler +namespace EggLink.DanhengServer.WebServer.Handler { internal class QueryGatewayHandler { @@ -14,7 +14,8 @@ namespace EggLink.DanhengServer.Server.Http.Handler var urlData = config.DownloadUrl; // build gateway proto - var gateServer = new GateServer() { + var gateServer = new GateServer() + { RegionName = config.GameServer.GameServerId, Ip = config.GameServer.PublicAddress, Port = config.GameServer.PublicPort, @@ -41,7 +42,7 @@ namespace EggLink.DanhengServer.Server.Http.Handler gateServer.LuaUrl = urlData.LuaUrl; gateServer.MdkResVersion = urlData.LuaUrl.Split('/')[^1].Split('_')[1]; } - + if (urlData.IfixUrl != null && urlData.IfixUrl.Length > 0) { gateServer.IfixUrl = urlData.IfixUrl; diff --git a/WebServer/Request/AuthRequest.cs b/WebServer/Request/AuthRequest.cs new file mode 100644 index 00000000..c9fe819c --- /dev/null +++ b/WebServer/Request/AuthRequest.cs @@ -0,0 +1,8 @@ +namespace EggLink.DanhengServer.WebServer.Request +{ + public class AuthAdminKeyRequestBody + { + public string admin_key { get; set; } = ""; + public string key_type { get; set; } = "XML"; + } +} diff --git a/WebServer/Request/ExecRequest.cs b/WebServer/Request/ExecRequest.cs new file mode 100644 index 00000000..1016acf5 --- /dev/null +++ b/WebServer/Request/ExecRequest.cs @@ -0,0 +1,9 @@ +namespace EggLink.DanhengServer.WebServer.Request +{ + public class AdminExecRequest + { + public string SessionId { get; set; } = ""; + public string Command { get; set; } = ""; + public int TargetUid { get; set; } = 0; + } +} diff --git a/WebServer/Response/AuthAdminKeyResponse.cs b/WebServer/Response/AuthAdminKeyResponse.cs new file mode 100644 index 00000000..1679c651 --- /dev/null +++ b/WebServer/Response/AuthAdminKeyResponse.cs @@ -0,0 +1,13 @@ +namespace EggLink.DanhengServer.WebServer.Response +{ + public class AuthAdminKeyResponse(int code, string message, AuthAdminKeyData? data) : BaseResponse(code, message, data) + { + } + + public class AuthAdminKeyData + { + public string RsaPublicKey { get; set; } = ""; + public string SessionId { get; set; } = ""; + public long ExpireTimeStamp { get; set; } = 0; + } +} diff --git a/WebServer/Response/BaseResponse.cs b/WebServer/Response/BaseResponse.cs new file mode 100644 index 00000000..69ef3a7b --- /dev/null +++ b/WebServer/Response/BaseResponse.cs @@ -0,0 +1,14 @@ +namespace EggLink.DanhengServer.WebServer.Response +{ + public class BaseResponse(int code, string message, T? data) + { + public int Code { get; set; } = code; + public string Message { get; set; } = message; + + public T? Data { get; set; } = data; + } + + public class BasicResponse(int code, string message) : BaseResponse(code, message, null) + { + } +} diff --git a/WebServer/Response/ExecuteCommandResponse.cs b/WebServer/Response/ExecuteCommandResponse.cs new file mode 100644 index 00000000..7dbd5975 --- /dev/null +++ b/WebServer/Response/ExecuteCommandResponse.cs @@ -0,0 +1,12 @@ +namespace EggLink.DanhengServer.WebServer.Response +{ + public class ExecuteCommandResponse(int code, string message, ExecuteCommandData? data = null) : BaseResponse(code, message, data) + { + } + + public class ExecuteCommandData + { + public string SessionId { get; set; } = ""; + public string Message { get; set; } = ""; + } +} diff --git a/WebServer/Server/MuipCommandSender.cs b/WebServer/Server/MuipCommandSender.cs new file mode 100644 index 00000000..62274908 --- /dev/null +++ b/WebServer/Server/MuipCommandSender.cs @@ -0,0 +1,31 @@ +using EggLink.DanhengServer.Command; + +namespace EggLink.DanhengServer.WebServer.Server +{ + public class MuipCommandSender(MuipSession session, Action action) : ICommandSender + { + public MuipSession Session { get; } = session; + public Action MsgAction { get; } = action; + public int SenderUid { get; set; } = session.Account?.Uid ?? 0; + + public bool HasPermission(string permission) + { + if (Session.IsAdmin) + { + return true; + } + + return Session.Account?.Permissions?.Contains(permission) ?? false; + } + + public void SendMsg(string msg) + { + MsgAction(msg); + } + + public int GetSender() + { + return SenderUid; + } + } +} diff --git a/WebServer/Server/MuipManager.cs b/WebServer/Server/MuipManager.cs new file mode 100644 index 00000000..2e9e8a36 --- /dev/null +++ b/WebServer/Server/MuipManager.cs @@ -0,0 +1,171 @@ +using EggLink.DanhengServer.Util; +using EggLink.DanhengServer.WebServer.Response; +using Org.BouncyCastle.Crypto.Parameters; +using System.Numerics; +using System.Security.Cryptography; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.X509; +using System.Text; + +namespace EggLink.DanhengServer.WebServer.Server +{ + public static class MuipManager + { + public delegate void ExecuteCommandDelegate(string message, MuipCommandSender sender); + public static event ExecuteCommandDelegate? OnExecuteCommand; + + public static string RsaPublicKey { get; private set; } = ""; + public static string RsaPrivateKey { get; private set; } = ""; + + public static Dictionary Sessions { get; } = []; + + public static AuthAdminKeyData? AuthAdminAndCreateSession(string key, string key_type) + { + if (ConfigManager.Config.MuipServer.AdminKey != key) + { + return null; + } + + var session = new MuipSession() + { + SessionId = Guid.NewGuid().ToString(), + RsaPublicKey = GetRsaKeyPair().Item1, + ExpireTimeStamp = DateTime.Now.AddMinutes(15).ToUnixSec(), + IsAdmin = true, + }; + + if (key_type == "PEM") + { + // convert to PEM + session.RsaPublicKey = XMLToPEM_Pub(session.RsaPublicKey); + } + + Sessions.Add(session.SessionId, session); + + var data = new AuthAdminKeyData + { + RsaPublicKey = session.RsaPublicKey, + SessionId = session.SessionId, + ExpireTimeStamp = session.ExpireTimeStamp, + }; + + return data; + } + + public static MuipSession? GetSession(string sessionId) + { + if (Sessions.TryGetValue(sessionId, out MuipSession? value)) + { + var session = value; + if (session.ExpireTimeStamp < DateTime.Now.ToUnixSec()) + { + Sessions.Remove(sessionId); + return null; + } + return session; + } + return null; + } + + public static ExecuteCommandResponse ExecuteCommand(string sessionId, string command, int targetUid) + { + if (Sessions.TryGetValue(sessionId, out MuipSession? value)) + { + var session = value; + if (session.ExpireTimeStamp < DateTime.Now.ToUnixSec()) + { + Sessions.Remove(sessionId); + return new(1, "Session has expired!"); + } + + var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(GetRsaKeyPair().Item2); + byte[] decrypted; + + try + { + decrypted = rsa.Decrypt(Convert.FromBase64String(command), RSAEncryptionPadding.Pkcs1); + } catch + { + return new(3, "Wrong encrypted key"); + } + + var commandStr = Encoding.UTF8.GetString(decrypted); + var returnStr = ""; + + var sync = Task.Run(() => OnExecuteCommand?.Invoke(commandStr, new MuipCommandSender(session, (msg) => + { + returnStr += msg + "\r\n"; + }) + { + SenderUid = targetUid, + })); + + sync.Wait(); + + return new(0, "Success", new() + { + SessionId = sessionId, + Message = Convert.ToBase64String(Encoding.UTF8.GetBytes(returnStr)), + }); + } + return new(2, "Session not found!"); + } + + /// + /// get rsa key pair + /// + /// item 1 is public key, item 2 is private key + public static (string, string) GetRsaKeyPair() + { + if (string.IsNullOrEmpty(RsaPublicKey) || string.IsNullOrEmpty(RsaPrivateKey)) + { + var rsa = new RSACryptoServiceProvider(2048); + RsaPublicKey = rsa.ToXmlString(false); + RsaPrivateKey = rsa.ToXmlString(true); + } + return (RsaPublicKey, RsaPrivateKey); + } + + + public static string XMLToPEM_Pub(string xmlpubkey) + { + var rsa = new RSACryptoServiceProvider(); + rsa.FromXmlString(xmlpubkey); + var p = rsa.ExportParameters(false); + RsaKeyParameters key = new RsaKeyParameters(false, new Org.BouncyCastle.Math.BigInteger(1, p.Modulus), new Org.BouncyCastle.Math.BigInteger(1, p.Exponent)); + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(key); + byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded(); + string publicKey = Convert.ToBase64String(serializedPublicBytes); + return Format(publicKey, true); + } + + + private static string Format(string key, bool type) + { + string result = string.Empty; + + int length = key.Length / 64; + for (int i = 0; i < length; i++) + { + int start = i * 64; + result = result + key.Substring(start, 64) + "\r\n"; + } + + result = result + key.Substring(length * 64); + if (type) + { + result = result.Insert(0, "-----BEGIN PUBLIC KEY-----\r\n"); + result += "\r\n-----END PUBLIC KEY-----"; + } + else + { + result = result.Insert(0, "-----BEGIN PRIVATE KEY-----\r\n"); + result += "\r\n-----END PRIVATE KEY-----"; + } + + return result; + } + } +} diff --git a/WebServer/Server/MuipSession.cs b/WebServer/Server/MuipSession.cs new file mode 100644 index 00000000..9002e3b4 --- /dev/null +++ b/WebServer/Server/MuipSession.cs @@ -0,0 +1,14 @@ +using EggLink.DanhengServer.Database.Account; + +namespace EggLink.DanhengServer.WebServer.Server +{ + public class MuipSession + { + public string RsaPublicKey { get; set; } = ""; + public string SessionId { get; set; } = ""; + public long ExpireTimeStamp { get; set; } = 0; + + public bool IsAdmin { get; set; } = false; + public AccountData? Account { get; set; } = null; + } +} diff --git a/WebServer/WebServer.csproj b/WebServer/WebServer.csproj index fb07be3e..3e1dff7d 100644 --- a/WebServer/WebServer.csproj +++ b/WebServer/WebServer.csproj @@ -6,12 +6,14 @@ enable EggLink.DanhengServer.WebServer Library + DanhengWebServer +