From 57ce0e183bec5cb474cc231e96720d01bc350a1a Mon Sep 17 00:00:00 2001 From: lvjia Date: Tue, 28 Apr 2026 12:47:05 +0800 Subject: [PATCH 1/3] Rewrite girl skin type handling --- Common/Database/Inventory/InventoryData.cs | 11 +++- GameServer/Command/Commands/CommandDebug.cs | 60 ++++++++++++++++++ .../Handlers/Girl/GirlSkin_ChangeSkinType.cs | 58 +++++++++++++++-- .../Packet/Recv/Login/HandlerReqLogin.cs | 62 +++++++++++++++++++ 4 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 GameServer/Command/Commands/CommandDebug.cs diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index c1ab3d1..043a622 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -17,6 +17,9 @@ public class InventoryData : BaseDatabaseDataHelper [SugarColumn(IsJson = true)] public Dictionary Skins { get; set; } = []; // Key: UniqueId + + [SugarColumn(IsJson = true)] + public Dictionary SkinTypesBySkinId { get; set; } = []; // Key: nSkinId, Value: client nType } public class BaseGameItemInfo @@ -67,7 +70,9 @@ public class GameWeaponInfo : GrowableItemInfo }; return proto; } -}public class GameSkinInfo : BaseGameItemInfo +} + +public class GameSkinInfo : BaseGameItemInfo { public uint SkinType { get; set; } public override Item ToProto() @@ -79,7 +84,7 @@ public class GameWeaponInfo : GrowableItemInfo Count = ItemCount, Flag = (uint)Flag, }; - proto.Slots[11] = SkinType; + proto.Slots[11] = Math.Min(SkinType, 1); return proto; } -} \ 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..fb8feb5 --- /dev/null +++ b/GameServer/Command/Commands/CommandDebug.cs @@ -0,0 +1,60 @@ +using MikuSB.Configuration; +using MikuSB.Enums.Player; +using MikuSB.Util; + +namespace MikuSB.GameServer.Command.Commands; + +[CommandInfo("debug", "Debug packet output", "/debug [on|off|simple|detail|file]", ["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" => EnableDebug(serverOption), + "file" => ToggleDebugFile(serverOption), + _ => "Usage: /debug [on|off|simple|detail|file]" + }; + + Logger.Info(message); + await arg.SendMsg(message); + } + + private static string EnableDebug(ServerOption serverOption) + { + serverOption.EnableDebug = true; + serverOption.DebugMessage = true; + serverOption.DebugDetailMessage = true; + return "Debug packet output enabled."; + } + + private static string DisableDebug(ServerOption serverOption) + { + serverOption.EnableDebug = false; + return "Debug packet output disabled."; + } + + private static string EnableSimpleDebug(ServerOption serverOption) + { + serverOption.EnableDebug = true; + serverOption.DebugMessage = true; + serverOption.DebugDetailMessage = false; + return "Simple debug packet output enabled."; + } + + private static string ToggleDebugFile(ServerOption serverOption) + { + serverOption.SavePersonalDebugFile = !serverOption.SavePersonalDebugFile; + return serverOption.SavePersonalDebugFile + ? "Personal debug file output enabled." + : "Personal debug file output disabled."; + } +} 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 fef7dac..d46c6d3 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; @@ -52,6 +54,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() } + }); + } } } From f6204134a9885dee81185762e0e76ca92d9810c8 Mon Sep 17 00:00:00 2001 From: lvjia Date: Tue, 28 Apr 2026 13:09:34 +0800 Subject: [PATCH 2/3] /debug Multiple languages Co-authored-by: Copilot --- .../Message/LanguageCHS.cs | 16 ++++++++++++++ .../Message/LanguageCHT.cs | 16 ++++++++++++++ .../Message/LanguageEN.cs | 16 ++++++++++++++ GameServer/Command/Commands/CommandDebug.cs | 21 +++++++++++++------ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index adf2c19..5b030e0 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -121,6 +121,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 @@ -237,6 +238,21 @@ public class GiveAllTextCHS public string WeaponAdded => "已添加 {0} 把武器给玩家!"; } +/// +/// 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 2851943..04b4ca5 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -121,6 +121,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 @@ -237,6 +238,21 @@ public class GiveAllTextCHT public string WeaponAdded => "已添加 {0} 把武器給玩家!"; } +/// +/// 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 8dcffd9..8ec2a67 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 @@ -206,6 +207,21 @@ public class GiveAllTextEN public string WeaponAdded => "Added {0} weapon(s) 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 index fb8feb5..a71da7a 100644 --- a/GameServer/Command/Commands/CommandDebug.cs +++ b/GameServer/Command/Commands/CommandDebug.cs @@ -1,6 +1,7 @@ using MikuSB.Configuration; using MikuSB.Enums.Player; using MikuSB.Util; +using MikuSB.Internationalization; namespace MikuSB.GameServer.Command.Commands; @@ -19,7 +20,7 @@ public class CommandDebug : ICommands "on" => EnableDebug(serverOption), "off" => DisableDebug(serverOption), "simple" => EnableSimpleDebug(serverOption), - "detail" => EnableDebug(serverOption), + "detail" => EnableDetailDebug(serverOption), "file" => ToggleDebugFile(serverOption), _ => "Usage: /debug [on|off|simple|detail|file]" }; @@ -33,13 +34,13 @@ public class CommandDebug : ICommands serverOption.EnableDebug = true; serverOption.DebugMessage = true; serverOption.DebugDetailMessage = true; - return "Debug packet output enabled."; + return I18NManager.Translate("Game.Command.Debug.Enabled"); } private static string DisableDebug(ServerOption serverOption) { serverOption.EnableDebug = false; - return "Debug packet output disabled."; + return I18NManager.Translate("Game.Command.Debug.Disabled"); } private static string EnableSimpleDebug(ServerOption serverOption) @@ -47,14 +48,22 @@ public class CommandDebug : ICommands serverOption.EnableDebug = true; serverOption.DebugMessage = true; serverOption.DebugDetailMessage = false; - return "Simple debug packet output enabled."; + 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 - ? "Personal debug file output enabled." - : "Personal debug file output disabled."; + ? I18NManager.Translate("Game.Command.Debug.FileEnabled") + : I18NManager.Translate("Game.Command.Debug.FileDisabled"); } } From 2f8d0c6afc1f904c13d9139adb32bd056c7ee1d5 Mon Sep 17 00:00:00 2001 From: lvjia Date: Tue, 28 Apr 2026 14:05:32 +0800 Subject: [PATCH 3/3] Fix debug command i18n keys --- GameServer/Command/Commands/CommandDebug.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GameServer/Command/Commands/CommandDebug.cs b/GameServer/Command/Commands/CommandDebug.cs index a71da7a..1127dce 100644 --- a/GameServer/Command/Commands/CommandDebug.cs +++ b/GameServer/Command/Commands/CommandDebug.cs @@ -5,7 +5,7 @@ using MikuSB.Internationalization; namespace MikuSB.GameServer.Command.Commands; -[CommandInfo("debug", "Debug packet output", "/debug [on|off|simple|detail|file]", ["dbg"], [PermEnum.Admin, PermEnum.Support])] +[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"); @@ -22,7 +22,7 @@ public class CommandDebug : ICommands "simple" => EnableSimpleDebug(serverOption), "detail" => EnableDetailDebug(serverOption), "file" => ToggleDebugFile(serverOption), - _ => "Usage: /debug [on|off|simple|detail|file]" + _ => I18NManager.Translate("Game.Command.Debug.Usage") }; Logger.Info(message);