diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index b1ef489..f00c396 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -20,6 +20,8 @@ public class InventoryData : BaseDatabaseDataHelper [SugarColumn(IsJson = true)] public Dictionary SupportCards { get; set; } = []; // Key: UniqueId + + public Dictionary SkinTypesBySkinId { get; set; } = []; // Key: nSkinId, Value: client nType } public class BaseGameItemInfo @@ -88,7 +90,7 @@ public class GameSkinInfo : BaseGameItemInfo Count = ItemCount, Flag = (uint)Flag, }; - proto.Slots[11] = SkinType; + proto.Slots[11] = Math.Min(SkinType, 1); return proto; } } @@ -114,4 +116,4 @@ public class GameSupportCardInfo : BaseGameItemInfo proto.Slots[1] = AffixId; return proto; } -} \ No newline at end of file +} diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 61ed2dc..3c9fd14 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -124,6 +124,7 @@ public class CommandTextCHS public HelpTextCHS Help { get; } = new(); public GirlTextCHS Girl { get; } = new(); public GiveAllTextCHS GiveAll { get; } = new(); + public DebugTextCHS Debug { get; } = new(); } #endregion @@ -244,6 +245,21 @@ public class GiveAllTextCHS public string GiveAllItems => "已向玩家添加 {0} 个 {1}!"; } +/// +/// path: Game.Command.Debug +/// +public class DebugTextCHS +{ + public string Desc => "调试包输出开关"; + public string Usage => "用法: /debug [on|off|simple|detail|file]"; + public string Enabled => "已启用调试包输出。"; + public string Disabled => "已禁用调试包输出。"; + public string SimpleEnabled => "已启用简单调试包输出。"; + public string DetailEnabled => "已启用详细调试包输出。"; + public string FileEnabled => "个人调试文件输出已启用。"; + public string FileDisabled => "个人调试文件输出已禁用。"; +} + #endregion #endregion \ No newline at end of file diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index f979e16..03d7eb1 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -124,6 +124,7 @@ public class CommandTextCHT public HelpTextCHT Help { get; } = new(); public GirlTextCHT Girl { get; } = new(); public GiveAllTextCHT GiveAll { get; } = new(); + public DebugTextCHT Debug { get; } = new(); } #endregion @@ -244,6 +245,21 @@ public class GiveAllTextCHT public string GiveAllItems => "已向玩家添加 {0} 個 {1}!"; } +/// +/// path: Game.Command.Debug +/// +public class DebugTextCHT +{ + public string Desc => "切換調試封包輸出"; + public string Usage => "用法: /debug [on|off|simple|detail|file]"; + public string Enabled => "已啟用調試封包輸出。"; + public string Disabled => "已停用調試封包輸出。"; + public string SimpleEnabled => "已啟用簡易調試封包輸出。"; + public string DetailEnabled => "已啟用詳細調試封包輸出。"; + public string FileEnabled => "個人調試檔案輸出已啟用。"; + public string FileDisabled => "個人調試檔案輸出已停用。"; +} + #endregion #endregion \ No newline at end of file diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index 242fcdc..302cc30 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -83,6 +83,7 @@ public class CommandTextEN public HelpTextEN Help { get; } = new(); public GirlTextEN Girl { get; } = new(); public GiveAllTextEN GiveAll { get; } = new(); + public DebugTextEN Debug { get; } = new(); } #endregion @@ -210,6 +211,21 @@ public class GiveAllTextEN public string GiveAllItems => "Added {0} {1} to player!"; } +/// +/// path: Game.Command.Debug +/// +public class DebugTextEN +{ + public string Desc => "Toggle debug packet output"; + public string Usage => "Usage: /debug [on|off|simple|detail|file]"; + public string Enabled => "Debug packet output enabled."; + public string Disabled => "Debug packet output disabled."; + public string SimpleEnabled => "Simple debug packet output enabled."; + public string DetailEnabled => "Detailed debug packet output enabled."; + public string FileEnabled => "Personal debug file output enabled."; + public string FileDisabled => "Personal debug file output disabled."; +} + #endregion #endregion \ No newline at end of file diff --git a/GameServer/Command/Commands/CommandDebug.cs b/GameServer/Command/Commands/CommandDebug.cs new file mode 100644 index 0000000..1127dce --- /dev/null +++ b/GameServer/Command/Commands/CommandDebug.cs @@ -0,0 +1,69 @@ +using MikuSB.Configuration; +using MikuSB.Enums.Player; +using MikuSB.Util; +using MikuSB.Internationalization; + +namespace MikuSB.GameServer.Command.Commands; + +[CommandInfo("debug", "Game.Command.Debug.Desc", "Game.Command.Debug.Usage", ["dbg"], [PermEnum.Admin, PermEnum.Support])] +public class CommandDebug : ICommands +{ + private static readonly Logger Logger = new("CommandManager"); + + [CommandDefault] + public async ValueTask ToggleDebug(CommandArg arg) + { + var option = arg.Args.FirstOrDefault()?.ToLowerInvariant() ?? "on"; + var serverOption = ConfigManager.Config.ServerOption; + var message = option switch + { + "on" => EnableDebug(serverOption), + "off" => DisableDebug(serverOption), + "simple" => EnableSimpleDebug(serverOption), + "detail" => EnableDetailDebug(serverOption), + "file" => ToggleDebugFile(serverOption), + _ => I18NManager.Translate("Game.Command.Debug.Usage") + }; + + Logger.Info(message); + await arg.SendMsg(message); + } + + private static string EnableDebug(ServerOption serverOption) + { + serverOption.EnableDebug = true; + serverOption.DebugMessage = true; + serverOption.DebugDetailMessage = true; + return I18NManager.Translate("Game.Command.Debug.Enabled"); + } + + private static string DisableDebug(ServerOption serverOption) + { + serverOption.EnableDebug = false; + return I18NManager.Translate("Game.Command.Debug.Disabled"); + } + + private static string EnableSimpleDebug(ServerOption serverOption) + { + serverOption.EnableDebug = true; + serverOption.DebugMessage = true; + serverOption.DebugDetailMessage = false; + return I18NManager.Translate("Game.Command.Debug.SimpleEnabled"); + } + + private static string EnableDetailDebug(ServerOption serverOption) + { + serverOption.EnableDebug = true; + serverOption.DebugMessage = true; + serverOption.DebugDetailMessage = true; + return I18NManager.Translate("Game.Command.Debug.DetailEnabled"); + } + + private static string ToggleDebugFile(ServerOption serverOption) + { + serverOption.SavePersonalDebugFile = !serverOption.SavePersonalDebugFile; + return serverOption.SavePersonalDebugFile + ? I18NManager.Translate("Game.Command.Debug.FileEnabled") + : I18NManager.Translate("Game.Command.Debug.FileDisabled"); + } +} diff --git a/GameServer/Server/CallGS/Handlers/Girl/GirlSkin_ChangeSkinType.cs b/GameServer/Server/CallGS/Handlers/Girl/GirlSkin_ChangeSkinType.cs index 268f890..e1a992b 100644 --- a/GameServer/Server/CallGS/Handlers/Girl/GirlSkin_ChangeSkinType.cs +++ b/GameServer/Server/CallGS/Handlers/Girl/GirlSkin_ChangeSkinType.cs @@ -1,4 +1,9 @@ -using MikuSB.Proto; +using MikuSB.Data; +using MikuSB.Database; +using MikuSB.Database.Inventory; +using MikuSB.Enums.Item; +using MikuSB.GameServer.Game.Player; +using MikuSB.Proto; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -11,9 +16,10 @@ public class GirlSkin_ChangeSkinType : ICallGSHandler public async Task Handle(Connection connection, string param, ushort seqNo) { var req = JsonSerializer.Deserialize(param); + var skinType = ClampClientSkinType(req?.Type ?? 0); var response = new JsonObject { - ["nType"] = req?.Type ?? 1, + ["nType"] = skinType, ["nSkinId"] = req?.SkinId }; if (req == null) @@ -21,16 +27,22 @@ public class GirlSkin_ChangeSkinType : ICallGSHandler await CallGSRouter.SendScript(connection, "GirlSkin_ChangeSkinType", response.ToJsonString()); return; } - + var player = connection.Player!; - var skinData = player.InventoryManager.GetSkinItem(req.SkinId); + var skinData = GetOrCreateSkinItem(player, req.SkinId); + if (skinData != null) + skinData.SkinType = skinType; + + player.InventoryManager.InventoryData.SkinTypesBySkinId ??= []; + player.InventoryManager.InventoryData.SkinTypesBySkinId[req.SkinId] = skinType; + DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData); + if (skinData == null) { await CallGSRouter.SendScript(connection, "GirlSkin_ChangeSkinType", response.ToJsonString()); return; } - skinData.SkinType = req.Type; var sync = new NtfSyncPlayer { Items = { skinData.ToProto() } @@ -38,6 +50,42 @@ public class GirlSkin_ChangeSkinType : ICallGSHandler await CallGSRouter.SendScript(connection, "GirlSkin_ChangeSkinType", response.ToJsonString(), sync); } + + internal static uint ClampClientSkinType(uint skinType) + { + return Math.Min(skinType, 1); + } + + internal static GameSkinInfo? GetOrCreateSkinItem(PlayerInstance player, uint skinId) + { + var inventoryData = player.InventoryManager.InventoryData; + if (inventoryData.Skins.TryGetValue(skinId, out var skinInfo)) + return skinInfo; + + if (!GameData.CardSkinData.TryGetValue(skinId, out var skinData)) + return null; + + var templateId = GameResourceTemplateId.FromGdpl(skinData.Genre, skinData.Detail, skinData.Particular, skinData.Level); + skinInfo = player.InventoryManager.GetSkinItemByTemplateId(templateId); + if (skinInfo != null) + { + inventoryData.Skins.Remove(skinInfo.UniqueId); + skinInfo.UniqueId = skinId; + } + else + { + skinInfo = new GameSkinInfo + { + UniqueId = skinId, + TemplateId = templateId, + ItemType = ItemTypeEnum.TYPE_CARD_SKIN, + ItemCount = 1 + }; + } + + inventoryData.Skins[skinId] = skinInfo; + return skinInfo; + } } internal sealed class ChangeSkinTypeParam diff --git a/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs b/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs index 8efef28..dcf12c6 100644 --- a/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs +++ b/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs @@ -4,12 +4,14 @@ using MikuSB.Database.Account; using MikuSB.Database.Player; using MikuSB.GameServer.Game.Player; using MikuSB.GameServer.Server.CallGS; +using MikuSB.GameServer.Server.CallGS.Handlers.Girl; using MikuSB.GameServer.Server.Packet.Send.Friend; using MikuSB.GameServer.Server.Packet.Send.Login; using MikuSB.GameServer.Server.Packet.Send.Misc; using MikuSB.Proto; using MikuSB.TcpSharp; using MikuSB.Util; +using System.Text.Json.Nodes; namespace MikuSB.GameServer.Server.Packet.Recv.Login; @@ -54,6 +56,66 @@ public class HandlerReqLogin : Handler await connection.Player.OnHeartBeat(); await connection.SendPacket(new PacketNtfUpdateFriend(connection.Player!)); + ApplySavedGirlSkinTypes(connection.Player!); await connection.SendPacket(new PacketNtfCallScript(connection.Player!.InventoryManager.InventoryData)); + await SendGirlSkinTypeOnLogin(connection); + } + + private static void ApplySavedGirlSkinTypes(PlayerInstance player) + { + var inventoryData = player.InventoryManager.InventoryData; + inventoryData.SkinTypesBySkinId ??= []; + var changed = false; + + foreach (var (skinId, skinType) in inventoryData.SkinTypesBySkinId.ToArray()) + { + var clamped = GirlSkin_ChangeSkinType.ClampClientSkinType(skinType); + if (clamped != skinType) + { + inventoryData.SkinTypesBySkinId[skinId] = clamped; + changed = true; + } + + var skinData = GirlSkin_ChangeSkinType.GetOrCreateSkinItem(player, skinId); + if (skinData != null && skinData.SkinType != clamped) + { + skinData.SkinType = clamped; + changed = true; + } + } + + if (changed) + DatabaseHelper.SaveDatabaseType(inventoryData); + } + + private static async Task SendGirlSkinTypeOnLogin(Connection connection) + { + var player = connection.Player; + if (player == null) + return; + + var inventoryData = player.InventoryManager.InventoryData; + inventoryData.SkinTypesBySkinId ??= []; + foreach (var (skinId, skinType) in inventoryData.SkinTypesBySkinId) + { + var clamped = GirlSkin_ChangeSkinType.ClampClientSkinType(skinType); + var skinData = GirlSkin_ChangeSkinType.GetOrCreateSkinItem(player, skinId); + var response = new JsonObject + { + ["nType"] = clamped, + ["nSkinId"] = skinId + }; + + if (skinData == null) + { + await CallGSRouter.SendScript(connection, "GirlSkin_ChangeSkinType", response.ToJsonString()); + continue; + } + + await CallGSRouter.SendScript(connection, "GirlSkin_ChangeSkinType", response.ToJsonString(), new NtfSyncPlayer + { + Items = { skinData.ToProto() } + }); + } } }