From 57ce0e183bec5cb474cc231e96720d01bc350a1a Mon Sep 17 00:00:00 2001 From: lvjia Date: Tue, 28 Apr 2026 12:47:05 +0800 Subject: [PATCH] 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() } + }); + } } }