From 57ce0e183bec5cb474cc231e96720d01bc350a1a Mon Sep 17 00:00:00 2001 From: lvjia Date: Tue, 28 Apr 2026 12:47:05 +0800 Subject: [PATCH 01/22] 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 02/22] /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 03/22] 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); From 069ee6aa2abd79f428a1ef4956b1c485cb5235fa Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Tue, 28 Apr 2026 21:45:32 +0800 Subject: [PATCH 04/22] add girl neuronic command --- .../Message/LanguageCHS.cs | 4 +- .../Message/LanguageCHT.cs | 4 +- .../Message/LanguageEN.cs | 4 +- GameServer/Command/Commands/CommandGirl.cs | 45 +++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index adf2c19..1827e27 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -218,11 +218,13 @@ public class GirlTextCHS public string Usage => "用法: /girl add -p -l -s\n" + - "用法: /girl level "; + "用法: /girl level \n" + + "用法: /girl neuronic "; public string NotFound => "角色不存在!"; public string Added => "已为玩家添加 {0} 个角色!"; public string UpdateLevel => "已将 {1} 个角色等级设置为 {0}!"; + public string UpdateNeuronicLevel => "已将 {1} 个角色的神经元等级设置为 {0}!"; } /// diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index 2851943..9900eb5 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -218,11 +218,13 @@ public class GirlTextCHT public string Usage => "用法: /girl add -p -l -s\n" + - "用法: /girl level "; + "用法: /girl level \n" + + "用法: /girl neuronic "; public string NotFound => "角色不存在!"; public string Added => "已為玩家新增 {0} 個角色!"; public string UpdateLevel => "已將 {1} 個角色等級設為 {0}!"; + public string UpdateNeuronicLevel => "已將 {1} 個角色的神經元等級設置為 {0}!"; } /// diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index 8dcffd9..c6af2a9 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -187,11 +187,13 @@ public class GirlTextEN public string Usage => "Usage: /girl add -p -l -s\n" + - "Usage: /girl level "; + "Usage: /girl level \n" + + "Usage: /girl neuronic "; public string NotFound => "Character not found!"; public string Added => "Granted {0} character(s) to player!"; public string UpdateLevel => "Set {1} character(s) to level {0}!"; + public string UpdateNeuronicLevel => "Set {1} character(s) Neuronic to level {0}!"; } /// diff --git a/GameServer/Command/Commands/CommandGirl.cs b/GameServer/Command/Commands/CommandGirl.cs index 3341ecf..afeff25 100644 --- a/GameServer/Command/Commands/CommandGirl.cs +++ b/GameServer/Command/Commands/CommandGirl.cs @@ -84,4 +84,49 @@ public class CommandGirl : ICommands level.ToString(), girls.Count.ToString())); } + + [CommandMethod("neuronic")] + public async ValueTask UpdateNeuronicLevel(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (!await arg.CheckArgCnt(2)) return; + + var guid = arg.GetInt(0); + var level = Math.Clamp(arg.GetInt(1), 0, 6); + var player = arg.Target!.Player!; + List girls = []; + + List spines = new List(); + for (int i = 0; i < 6; i++) + spines.Add(i < level ? 511u : 0u); + + uint proLevel = (uint)(spines.Count(x => x == 511) / 2); + + if (guid == -1) + { + foreach (var girl in player.CharacterManager.CharacterData.Characters) + { + girl.Spines = spines; + girl.ProLevel = proLevel; + girls.Add(girl); + } + } + else + { + var girl = player.CharacterManager.GetCharacterByGUID((uint)guid); + if (girl == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.Girl.NotFound")); + return; + } + girl.Spines = spines; + girl.ProLevel = proLevel; + girls.Add(girl); + } + + if (girls.Count > 0) await player.SendPacket(new PacketNtfCallScript(girls)); + await arg.SendMsg(I18NManager.Translate("Game.Command.Girl.UpdateNeuronicLevel", + level.ToString(), + girls.Count.ToString())); + } } \ No newline at end of file From 5f0de1a9f030ebc4ed8f0f09dded102811c0f7d2 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:08:23 +0800 Subject: [PATCH 05/22] add support card giveall command NOTE: need to delete old database because im moving support card to different column --- Common/Database/Inventory/InventoryData.cs | 29 +++++++++++++- .../Message/LanguageCHS.cs | 13 +++--- .../Message/LanguageCHT.cs | 13 +++--- .../Message/LanguageEN.cs | 9 +++-- GameServer/Command/Commands/CommandGiveAll.cs | 40 ++++++++++++++++++- GameServer/Game/Inventory/InventoryManager.cs | 30 +++++++++++--- GameServer/Game/Player/PlayerInstance.cs | 4 -- .../Packet/Send/Misc/PacketNtfCallScript.cs | 16 ++++++++ 8 files changed, 127 insertions(+), 27 deletions(-) diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index 495e18d..b1ef489 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 SupportCards { get; set; } = []; // Key: UniqueId } public class BaseGameItemInfo @@ -72,7 +75,8 @@ public class GameWeaponInfo : GrowableItemInfo }; return proto; } -}public class GameSkinInfo : BaseGameItemInfo +} +public class GameSkinInfo : BaseGameItemInfo { public uint SkinType { get; set; } public override Item ToProto() @@ -87,4 +91,27 @@ public class GameWeaponInfo : GrowableItemInfo proto.Slots[11] = SkinType; return proto; } +} + + +public class GameSupportCardInfo : BaseGameItemInfo +{ + public uint AffixId { get; set; } + public override Item ToProto() + { + var proto = new Item + { + Id = UniqueId, + Template = TemplateId, + Count = ItemCount, + Flag = (uint)Flag, + Enhance = new Enhance + { + Level = Level, + Exp = Exp + } + }; + 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 1827e27..24e601d 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -35,6 +35,8 @@ public class ServerTextCHS /// public class WordTextCHS { + public string SupportCard => "支援卡"; + public string Weapon => "武器"; public string Rank => "星魂"; public string Avatar => "角色"; public string Material => "材料"; @@ -232,11 +234,12 @@ public class GirlTextCHS /// public class GiveAllTextCHS { - public string Desc => "给玩家所有物品\n" + - "备注: -1 代表全部"; - public string Usage => "用法: /giveall weapon -p -l"; - public string WeaponNotFound => "找不到武器!"; - public string WeaponAdded => "已添加 {0} 把武器给玩家!"; + public string Desc => "给予玩家所有物品\n" + + "注意:-1 表示全部"; + public string Usage => "用法:/giveall weapon -p<特定> -l<等级>\n" + + "用法:/giveall card -p<特定> -l<等级>"; + public string NotFound => "未找到 {0}!"; + public string GiveAllItems => "已向玩家添加 {0} 个 {1}!"; } #endregion diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index 9900eb5..deadf27 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -35,6 +35,8 @@ public class ServerTextCHT /// public class WordTextCHT { + public string SupportCard => "支援卡"; + public string Weapon => "武器"; public string Rank => "星魂"; public string Avatar => "角色"; public string Material => "材料"; @@ -232,11 +234,12 @@ public class GirlTextCHT /// public class GiveAllTextCHT { - public string Desc => "給玩家所有物品\n" + - "備註: -1 代表全部"; - public string Usage => "用法: /giveall weapon -p -l"; - public string WeaponNotFound => "找不到武器!"; - public string WeaponAdded => "已添加 {0} 把武器給玩家!"; + public string Desc => "給予玩家所有物品\n" + + "注意:-1 表示全部"; + public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + + "用法:/giveall card -p<特定> -l<等級>"; + public string NotFound => "未找到 {0}!"; + public string GiveAllItems => "已向玩家添加 {0} 個 {1}!"; } #endregion diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index c6af2a9..cc81189 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -38,7 +38,7 @@ public class WordTextEN public string Star => "Star"; public string Valk => "Valkyrie"; public string Material => "Material"; - public string Stigmata => "Stigmata"; + public string SupportCard => "Support Card"; public string Weapon => "Weapon"; public string Banner => "Gacha"; public string Activity => "Activity"; @@ -203,9 +203,10 @@ public class GiveAllTextEN { public string Desc => "Give all items to player\n"+ "Note: -1 means all"; - public string Usage => "Usage: /giveall weapon -p -l"; - public string WeaponNotFound => "Weapon not found!"; - public string WeaponAdded => "Added {0} weapon(s) to player!"; + public string Usage => "Usage: /giveall weapon -p -l\n" + + "Usage: /giveall card -p -l"; + public string NotFound => "{0} not found!"; + public string GiveAllItems => "Added {0} {1} to player!"; } #endregion diff --git a/GameServer/Command/Commands/CommandGiveAll.cs b/GameServer/Command/Commands/CommandGiveAll.cs index 50cb0fd..72864fc 100644 --- a/GameServer/Command/Commands/CommandGiveAll.cs +++ b/GameServer/Command/Commands/CommandGiveAll.cs @@ -36,12 +36,48 @@ public class CommandGiveAll : ICommands var weapon = await player.InventoryManager!.AddWeaponItem(ItemTypeEnum.TYPE_WEAPON, (uint)detail,(uint)particular,1,(uint)level,false); if (weapon == null) { - await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.WeaponNotFound")); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.Weapon"))); return; } weapons.Add(weapon); } if (weapons.Count > 0) await player.SendPacket(new PacketNtfCallScript(weapons)); - await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.WeaponAdded", weapons.Count.ToString())); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", + I18NManager.Translate("Word.Weapon"), weapons.Count.ToString())); + } + + [CommandMethod("card")] + public async ValueTask GiveAllSupportCard(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (await arg.GetOption('p') is not int particular) return; + if (await arg.GetOption('l') is not int level) return; + + var detail = arg.GetInt(0); + var player = arg.Target!.Player!; + List supportCards = []; + if (detail == -1) + { + // add all + foreach (var config in GameData.SupportCardData) + { + var supportCard = await player.InventoryManager! + .AddSupportCardItem(config.Detail, config.Particular, config.Level, (uint)level, false); + if (supportCard != null) supportCards.Add(supportCard); + } + } + else + { + var supportCard = await player.InventoryManager!.AddSupportCardItem((uint)detail, (uint)particular, 1, (uint)level, false); + if (supportCard == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.SupportCard"))); + return; + } + supportCards.Add(supportCard); + } + if (supportCards.Count > 0) await player.SendPacket(new PacketNtfCallScript(supportCards)); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", + I18NManager.Translate("Word.SupportCard"), supportCards.Count.ToString())); } } \ No newline at end of file diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index 28d21c2..f1c8a7e 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -110,26 +110,44 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) return arInfo; } - public async ValueTask AddSupportCardItem(uint detail, uint particular, uint level = 1, bool sendPacket = true) + public async ValueTask AddSupportCardItem(uint detail, uint particular, uint level = 1, uint cardLevel = 1, bool sendPacket = true) { const ItemTypeEnum genre = ItemTypeEnum.TYPE_SUPPORT; + var spCard = GameData.SupportCardData.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level); + if (spCard == null) return null; var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level); - if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null; - - var info = new BaseGameItemInfo + cardLevel = Math.Clamp(cardLevel, 1, spCard.MaxLevel); + var info = new GameSupportCardInfo { TemplateId = templateId, UniqueId = InventoryData.NextUniqueUid++, ItemType = genre, - ItemCount = 1 + ItemCount = 1, + Level = cardLevel, }; - InventoryData.Items[info.UniqueId] = info; + InventoryData.SupportCards[info.UniqueId] = info; if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([info])); return info; } + public GameSupportCardInfo? GetSupportCardItem(uint uniqueId) + { + return InventoryData.SupportCards.GetValueOrDefault(uniqueId); + } + + public GameSupportCardInfo? GetSupportCardByTemplateId(ulong templateId) + { + return InventoryData.SupportCards.Values.FirstOrDefault(x => x.TemplateId == templateId); + } + + public GameSupportCardInfo? GetSupportCardItemGDPL(ItemTypeEnum genre, uint detail, uint particular, uint level) + { + var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level); + return InventoryData.SupportCards.Values.FirstOrDefault(x => x.TemplateId == templateId); + } + public async ValueTask AddManifestationItem(ItemTypeEnum genre, uint detail, uint particular, uint level = 1, bool sendPacket = true) { if (genre != ItemTypeEnum.TYPE_MANIFESTATION) return null; diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index 04995c8..e8f9a72 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -66,10 +66,6 @@ public class PlayerInstance(PlayerGameData data) { await CharacterManager.AddCharacter((ItemTypeEnum)card.Genre, card.Detail, card.Particular, card.Level, sendPacket:false); } - foreach (var sc in GameData.SupportCardData) - { - await InventoryManager.AddSupportCardItem(sc.Detail, sc.Particular, sc.Level, sendPacket: false); - } foreach (var supplies in GameData.AllSuppliesData) { await InventoryManager.AddSuppliesItem(supplies, 90000, false); diff --git a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs index 3bc08f4..7486522 100644 --- a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs +++ b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs @@ -53,6 +53,21 @@ public class PacketNtfCallScript : BasePacket SetData(proto); } + public PacketNtfCallScript(List cards) : base(CmdIds.NtfScript) + { + var proto = new NtfCallScript + { + Api = "", + Arg = "{}", + ExtraSync = new NtfSyncPlayer + { + Items = { cards.Select(x => x.ToProto()) } + } + }; + + SetData(proto); + } + public PacketNtfCallScript(InventoryData inventory) : base(CmdIds.NtfScript) { var proto = new NtfCallScript @@ -65,6 +80,7 @@ public class PacketNtfCallScript : BasePacket foreach (var item in inventory.Items.Values) extraSync.Items.Add(item.ToProto()); foreach (var skin in inventory.Skins.Values) extraSync.Items.Add(skin.ToProto()); foreach (var weapon in inventory.Weapons.Values) extraSync.Items.Add(weapon.ToProto()); + foreach (var supportCard in inventory.SupportCards.Values) extraSync.Items.Add(supportCard.ToProto()); proto.ExtraSync = extraSync; SetData(proto); } From 8d6e0d763879753353537805be3af88db957f676 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:48:14 +0800 Subject: [PATCH 06/22] add weapon skin & command for it --- Common/Data/Excel/WeaponSkinExcel.cs | 24 ++++++++++ Common/Data/GameData.cs | 1 + Common/Database/Character/CharacterData.cs | 2 + .../Message/LanguageCHS.cs | 6 ++- .../Message/LanguageCHT.cs | 2 + .../Message/LanguageEN.cs | 3 +- GameServer/Command/Commands/CommandGiveAll.cs | 34 ++++++++++++++ GameServer/Game/Inventory/InventoryManager.cs | 22 +++++++++ .../Handlers/Girl/GirlWeaponSkin_Change.cs | 47 +++++++++++++++++++ 9 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 Common/Data/Excel/WeaponSkinExcel.cs create mode 100644 GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs diff --git a/Common/Data/Excel/WeaponSkinExcel.cs b/Common/Data/Excel/WeaponSkinExcel.cs new file mode 100644 index 0000000..a51d9c1 --- /dev/null +++ b/Common/Data/Excel/WeaponSkinExcel.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("weapon_skin.json")] +public class WeaponSkinExcel : ExcelResource +{ + public uint Genre { get; set; } + public uint Detail { get; set; } + public uint Particular { get; set; } + public uint Level { get; set; } + public string I18n { get; set; } = ""; + + public override uint GetId() + { + return (uint)I18n.GetHashCode(); + } + + public override void Loaded() + { + GameData.WeaponSkinData.Add(GetId(), this); + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 45cf34c..48152f4 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -19,6 +19,7 @@ public static class GameData public static Dictionary SpineData { get; private set; } = []; public static Dictionary NodeConditionData { get; private set; } = []; public static List SupportCardData { get; private set; } = []; + public static Dictionary WeaponSkinData { get; private set; } = []; } public static class GameResourceTemplateId diff --git a/Common/Database/Character/CharacterData.cs b/Common/Database/Character/CharacterData.cs index 3cbb890..2330f96 100644 --- a/Common/Database/Character/CharacterData.cs +++ b/Common/Database/Character/CharacterData.cs @@ -23,6 +23,7 @@ public class CharacterInfo public int Trust { get; set; } public uint WeaponUniqueId { get; set; } public uint SkinId { get; set; } + public uint WeaponSkinId { get; set; } public ItemFlagEnum Flag { get; set; } = ItemFlagEnum.FLAG_READED; public uint Expiration { get; set; } [SugarColumn(IsJson = true)] public List UnlockedSkin { get; set; } = []; @@ -57,6 +58,7 @@ public class CharacterInfo proto.Slots[4] = WeaponUniqueId; proto.Slots[5] = SkinId; + proto.Slots[6] = WeaponSkinId; foreach (var (slot, uid) in SupportSlots) proto.Slots[slot] = uid; diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 24e601d..61ed2dc 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -35,6 +35,7 @@ public class ServerTextCHS /// public class WordTextCHS { + public string WeaponSkin => "武器皮肤"; public string SupportCard => "支援卡"; public string Weapon => "武器"; public string Rank => "星魂"; @@ -236,8 +237,9 @@ public class GiveAllTextCHS { public string Desc => "给予玩家所有物品\n" + "注意:-1 表示全部"; - public string Usage => "用法:/giveall weapon -p<特定> -l<等级>\n" + - "用法:/giveall card -p<特定> -l<等级>"; + public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + + "用法:/giveall weaponskin -p<特定>\n" + + "用法:/giveall card -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 个 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index deadf27..f979e16 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -35,6 +35,7 @@ public class ServerTextCHT /// public class WordTextCHT { + public string WeaponSkin => "武器外觀"; public string SupportCard => "支援卡"; public string Weapon => "武器"; public string Rank => "星魂"; @@ -237,6 +238,7 @@ public class GiveAllTextCHT public string Desc => "給予玩家所有物品\n" + "注意:-1 表示全部"; public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + + "用法:/giveall weaponskin -p<特定>\n" + "用法:/giveall card -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 個 {1}!"; diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index cc81189..242fcdc 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -35,7 +35,7 @@ public class ServerTextEN /// public class WordTextEN { - public string Star => "Star"; + public string WeaponSkin => "Weapon Skin"; public string Valk => "Valkyrie"; public string Material => "Material"; public string SupportCard => "Support Card"; @@ -204,6 +204,7 @@ public class GiveAllTextEN public string Desc => "Give all items to player\n"+ "Note: -1 means all"; public string Usage => "Usage: /giveall weapon -p -l\n" + + "Usage: /giveall weaponskin -p\n" + "Usage: /giveall card -p -l"; public string NotFound => "{0} not found!"; public string GiveAllItems => "Added {0} {1} to player!"; diff --git a/GameServer/Command/Commands/CommandGiveAll.cs b/GameServer/Command/Commands/CommandGiveAll.cs index 72864fc..6787528 100644 --- a/GameServer/Command/Commands/CommandGiveAll.cs +++ b/GameServer/Command/Commands/CommandGiveAll.cs @@ -80,4 +80,38 @@ public class CommandGiveAll : ICommands await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", I18NManager.Translate("Word.SupportCard"), supportCards.Count.ToString())); } + + [CommandMethod("weaponskin")] + public async ValueTask GiveAllWeaponSkin(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (await arg.GetOption('p') is not int particular) return; + + var detail = arg.GetInt(0); + var player = arg.Target!.Player!; + List weaponSkins = []; + if (detail == -1) + { + // add all + foreach (var config in GameData.WeaponSkinData.Values) + { + var weaponSkin = await player.InventoryManager! + .AddWeaponSkinItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, 1, false); + if (weaponSkin != null) weaponSkins.Add(weaponSkin); + } + } + else + { + var weaponSkin = await player.InventoryManager!.AddWeaponSkinItem(ItemTypeEnum.TYPE_WEAPON, (uint)detail, (uint)particular, 1, false); + if (weaponSkin == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.WeaponSkin"))); + return; + } + weaponSkins.Add(weaponSkin); + } + if (weaponSkins.Count > 0) await player.SendPacket(new PacketNtfCallScript(weaponSkins)); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", + I18NManager.Translate("Word.WeaponSkin"), weaponSkins.Count.ToString())); + } } \ No newline at end of file diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index f1c8a7e..c86f57a 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -216,4 +216,26 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) return itemInfo; } + + public async ValueTask AddWeaponSkinItem(ItemTypeEnum genre, uint detail, uint particular, uint level = 1, bool sendPacket = true) + { + if (genre != ItemTypeEnum.TYPE_WEAPON_SKIN) return null; + var skinData = GameData.WeaponSkinData.Values.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level); + if (skinData == null) return null; + + var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level); + if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null; + var skinInfo = new BaseGameItemInfo + { + TemplateId = templateId, + UniqueId = InventoryData.NextUniqueUid++, + ItemType = ItemTypeEnum.TYPE_WEAPON_SKIN, + ItemCount = 1 + }; + InventoryData.Items[skinInfo.UniqueId] = skinInfo; + + if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([skinInfo])); + + return skinInfo; + } } \ No newline at end of file diff --git a/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs b/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs new file mode 100644 index 0000000..01708fe --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs @@ -0,0 +1,47 @@ +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Girl; + +[CallGSApi("GirlWeaponSkin_Change")] +public class GirlWeaponSkin_Change : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null) + { + await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}"); + return; + } + + var player = connection.Player!; + var cardData = player.CharacterManager.GetCharacterByGUID(req.CardId); + if (cardData == null) return; + var skinData = player.InventoryManager.GetNormalItem(req.SkinId); + if (skinData == null) + { + await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{\"err\":\"error.BadParam\"}"); + return; + } + + cardData.WeaponSkinId = req.SkinId; + var sync = new NtfSyncPlayer + { + Items = { cardData.ToProto() } + }; + + await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}", sync); + } +} + +internal sealed class GirlWeaponSkinParam +{ + [JsonPropertyName("nCardId")] + public uint CardId { get; set; } + + [JsonPropertyName("nSkinId")] + public uint SkinId { get; set; } +} From 0cf0e3beb4206cdfd75ab39e74306fd45d0bcb55 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:36:17 +0800 Subject: [PATCH 07/22] fix weapon break limit --- GameServer/Command/Commands/CommandGiveAll.cs | 2 +- GameServer/Game/Inventory/InventoryManager.cs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/GameServer/Command/Commands/CommandGiveAll.cs b/GameServer/Command/Commands/CommandGiveAll.cs index 6787528..fb4a8bd 100644 --- a/GameServer/Command/Commands/CommandGiveAll.cs +++ b/GameServer/Command/Commands/CommandGiveAll.cs @@ -18,7 +18,7 @@ public class CommandGiveAll : ICommands if (await arg.GetOption('l') is not int level) return; var detail = arg.GetInt(0); - level = Math.Clamp(level, 1, 80); + level = Math.Clamp(level, 1, 90); var player = arg.Target!.Player!; List weapons = []; if (detail == -1) diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index c86f57a..fc9b088 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -24,7 +24,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) TemplateId = templateId, UniqueId = InventoryData.NextUniqueUid++, Level = weaponLevel, - Break = weaponData.InitBreak, + Break = GetWeaponBreak(weaponLevel), ItemType = ItemTypeEnum.TYPE_WEAPON, ItemCount = 1 }; @@ -35,6 +35,17 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) return weaponInfo; } + private static uint GetWeaponBreak(uint level) + { + if (level <= 20) return 1; + if (level <= 40) return 2; + if (level <= 60) return 3; + if (level <= 70) return 4; + if (level <= 80) return 5; + if (level <= 90) return 6; + return 7; + } + public GameWeaponInfo? GetWeaponItem(uint uniqueId) { return InventoryData.Weapons.GetValueOrDefault(uniqueId); From ee7948674837502d19f554963865ddf779e95b1d Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:46:44 +0800 Subject: [PATCH 08/22] v1.2 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 3b8e8d5..240e13a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v=1.1 \ No newline at end of file +v=1.2 \ No newline at end of file From 5af23a8113b5869980f9da823a15d41af2bd3865 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:47:21 +0800 Subject: [PATCH 09/22] unlock more stage for operation --- Common/Data/Excel/DailyLevelExcel.cs | 14 ++++ Common/Data/GameData.cs | 2 + GameServer/Game/Player/PlayerInstance.cs | 67 +++++++++++-------- .../Handlers/Daily/Daily_SetSelectSuit.cs | 26 +++++++ .../Packet/Send/Misc/PacketNtfCallScript.cs | 31 ++++++++- 5 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 Common/Data/Excel/DailyLevelExcel.cs create mode 100644 GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs diff --git a/Common/Data/Excel/DailyLevelExcel.cs b/Common/Data/Excel/DailyLevelExcel.cs new file mode 100644 index 0000000..12d0974 --- /dev/null +++ b/Common/Data/Excel/DailyLevelExcel.cs @@ -0,0 +1,14 @@ +namespace MikuSB.Data.Excel; + +[ResourceEntity("daily_level.json")] +public class DailyLevelExcel : ExcelResource +{ + public uint ID { get; set; } + + public override uint GetId() => ID; + + public override void Loaded() + { + GameData.DailyLevelData.Add(ID, this); + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 48152f4..fabd1fe 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -20,6 +20,8 @@ public static class GameData public static Dictionary NodeConditionData { get; private set; } = []; public static List SupportCardData { get; private set; } = []; public static Dictionary WeaponSkinData { get; private set; } = []; + public static Dictionary DailyLevelData { get; private set; } = []; + } public static class GameResourceTemplateId diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index e8f9a72..1dc1137 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -79,34 +79,6 @@ public class PlayerInstance(PlayerGameData data) await LineupManager.UpdateLineup(1, selected[0], selected[1], selected[2],false); - var bootstrapAttrs = BuildLobbyBootstrapAttrs(); - var existingAttrs = Data.Attrs - .ToDictionary(x => (x.Gid, x.Sid)); - var seenAttrs = new HashSet<(uint Gid, uint Sid)>(); - - foreach (var (gid, sid, value) in bootstrapAttrs) - { - if (!seenAttrs.Add((gid, sid))) - continue; - - if (existingAttrs.TryGetValue((gid, sid), out var attr)) - { - if (attr.Val < value) - attr.Val = value; - - continue; - } - - var newAttr = new PlayerAttr - { - Gid = gid, - Sid = sid, - Val = value - }; - - Data.Attrs.Add(newAttr); - existingAttrs[(gid, sid)] = newAttr; - } }); t.Wait(); @@ -226,6 +198,7 @@ public class PlayerInstance(PlayerGameData data) public Proto.Player ToPlayerProto() { + BuildPlayerAttr(); var displayName = PlayerGameData.NormalizeDisplayName(Data.Name); var proto = new Proto.Player { @@ -293,6 +266,38 @@ public class PlayerInstance(PlayerGameData data) return (gid << 16) | sid; } + public void BuildPlayerAttr() + { + var bootstrapAttrs = BuildLobbyBootstrapAttrs(); + var existingAttrs = Data.Attrs + .ToDictionary(x => (x.Gid, x.Sid)); + var seenAttrs = new HashSet<(uint Gid, uint Sid)>(); + + foreach (var (gid, sid, value) in bootstrapAttrs) + { + if (!seenAttrs.Add((gid, sid))) + continue; + + if (existingAttrs.TryGetValue((gid, sid), out var attr)) + { + if (attr.Val < value) + attr.Val = value; + + continue; + } + + var newAttr = new PlayerAttr + { + Gid = gid, + Sid = sid, + Val = value + }; + + Data.Attrs.Add(newAttr); + existingAttrs[(gid, sid)] = newAttr; + } + } + private static IEnumerable<(uint Gid, uint Sid, uint Value)> BuildLobbyBootstrapAttrs() { // GuideLogic uses group 4. Value 999 is safely above every configured step count, @@ -337,6 +342,12 @@ public class PlayerInstance(PlayerGameData data) yield return (22, levelId, 1_700_000_000); } + foreach (var levelId in GameData.DailyLevelData.Keys) + { + yield return (21, levelId, 7); + yield return (22, levelId, 1_700_000_000); + } + // Main Scene 0 mean default scene yield return (132, 1, 0); } diff --git a/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs b/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs new file mode 100644 index 0000000..2d51857 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs @@ -0,0 +1,26 @@ +using System.Text.Json; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Daily; + +[CallGSApi("Daily_SetSelectSuit")] +public class Daily_SetSelectSuit : ICallGSHandler +{ + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null) + { + await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}"); + return; + } + var rsp = $"{{\"SuitId\":{req.Suit}}}"; + await CallGSRouter.SendScript(connection, "Daily_SetSelectSuit", rsp); + } +} + +internal sealed class GirlWeaponSkinParam +{ + public uint Type { get; set; } + public uint Suit { get; set; } +} diff --git a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs index 7486522..0fde531 100644 --- a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs +++ b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs @@ -1,6 +1,6 @@ using MikuSB.Database.Character; using MikuSB.Database.Inventory; -using MikuSB.GameServer.Game.Inventory; +using MikuSB.GameServer.Game.Player; using MikuSB.Proto; using MikuSB.TcpSharp; @@ -84,4 +84,33 @@ public class PacketNtfCallScript : BasePacket proto.ExtraSync = extraSync; SetData(proto); } + + public PacketNtfCallScript(PlayerInstance Player) : base(CmdIds.NtfScript) + { + Player.BuildPlayerAttr(); + var proto = new NtfCallScript + { + Api = "", + Arg = "{}" + }; + var sync = new NtfSyncPlayer(); + foreach (var x in Player.Data.Attrs) + { + uint gid = x.Gid; + uint sid = x.Sid; + uint val = x.Val; + + if (gid == 0) + { + sync.Custom[sid] = val; + continue; + } + + sync.Custom[Player.ToPackedAttrKey(gid, sid)] = val; + sync.Custom[Player.ToShiftedAttrKey(gid, sid)] = val; + } + proto.ExtraSync = sync; + + SetData(proto); + } } From 720f56c708a376337d7a95c110b6ca90fec9e595 Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 29 Apr 2026 06:59:22 +0900 Subject: [PATCH 10/22] fix --- Common/Database/Inventory/InventoryData.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index f00c396..db9888c 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -21,6 +21,7 @@ public class InventoryData : BaseDatabaseDataHelper [SugarColumn(IsJson = true)] public Dictionary SupportCards { get; set; } = []; // Key: UniqueId + [SugarColumn(IsJson = true)] public Dictionary SkinTypesBySkinId { get; set; } = []; // Key: nSkinId, Value: client nType } From c61ac08dd3e18d2cebb0fb8a91ad96e538c56bac Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 29 Apr 2026 09:01:20 +0900 Subject: [PATCH 11/22] Fixed a critical issue with in-game chat. --- GameServer/Command/CommandSender.cs | 4 +-- GameServer/Game/Player/ChatMessageHelper.cs | 27 +++++++++++++++++++++ GameServer/Game/Player/PlayerInstance.cs | 4 +-- 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 GameServer/Game/Player/ChatMessageHelper.cs diff --git a/GameServer/Command/CommandSender.cs b/GameServer/Command/CommandSender.cs index 0486421..e74e794 100644 --- a/GameServer/Command/CommandSender.cs +++ b/GameServer/Command/CommandSender.cs @@ -38,9 +38,9 @@ public class PlayerCommandSender(PlayerInstance player) : ICommandSender Type = ChatType.Friend, Sender = (uint)ConfigManager.Config.ServerOption.ServerProfile.Uid, Recver = (uint)Player.Uid, - Text = msg, + Text = ChatMessageHelper.NormalizeForClient(msg), Profile = Player.ToServerFriendProto(), - TimeStamp = (uint)Extensions.GetUnixMs() + TimeStamp = ChatMessageHelper.BuildClientTimestamp() }; await Player.SendPacket(CmdIds.NtfFriendChat, data); } diff --git a/GameServer/Game/Player/ChatMessageHelper.cs b/GameServer/Game/Player/ChatMessageHelper.cs new file mode 100644 index 0000000..bebdf46 --- /dev/null +++ b/GameServer/Game/Player/ChatMessageHelper.cs @@ -0,0 +1,27 @@ +using System.Text.RegularExpressions; + +namespace MikuSB.GameServer.Game.Player; + +public static partial class ChatMessageHelper +{ + [GeneratedRegex(@"\s+")] + private static partial Regex MultiWhitespaceRegex(); + + public static uint BuildClientTimestamp() + { + return (uint)MikuSB.Util.Extensions.Extensions.GetUnixSec(); + } + + public static string NormalizeForClient(string? text) + { + if (string.IsNullOrWhiteSpace(text)) + return string.Empty; + + var normalized = text + .Replace("\r\n", " ") + .Replace('\r', ' ') + .Replace('\n', ' '); + + return MultiWhitespaceRegex().Replace(normalized, " ").Trim(); + } +} diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index 1dc1137..a424c18 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -160,9 +160,9 @@ public class PlayerInstance(PlayerGameData data) Sender = sendUid, Recver = recvUid, Emoji = emojiId ?? 0, - Text = message ?? "", + Text = ChatMessageHelper.NormalizeForClient(message), Profile = Data.ToProfileProto(), - TimeStamp = (uint)Extensions.GetUnixMs() + TimeStamp = ChatMessageHelper.BuildClientTimestamp() }; await SendPacket(CmdIds.NtfFriendChat, data); From 3611624073dfcced30bc3e37c40100563d93ac23 Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 29 Apr 2026 09:01:38 +0900 Subject: [PATCH 12/22] Update version.txt --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 240e13a..ae6845b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v=1.2 \ No newline at end of file +v=1.3 \ No newline at end of file From d1102b444c09ceafb65026bd3870f802ba02d63b Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 29 Apr 2026 09:40:36 +0900 Subject: [PATCH 13/22] Implement Weapon_Break --- Common/Data/Excel/BreakExcel.cs | 34 +++++++ Common/Data/GameData.cs | 1 + .../CallGS/Handlers/Weapon/Weapon_Break.cs | 98 +++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 Common/Data/Excel/BreakExcel.cs create mode 100644 GameServer/Server/CallGS/Handlers/Weapon/Weapon_Break.cs diff --git a/Common/Data/Excel/BreakExcel.cs b/Common/Data/Excel/BreakExcel.cs new file mode 100644 index 0000000..5abd201 --- /dev/null +++ b/Common/Data/Excel/BreakExcel.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("break.json")] +public class BreakExcel : ExcelResource +{ + [JsonProperty("ID")] public int Id { get; set; } + + [JsonProperty("Items1")] public List> Items1 { get; set; } = []; + [JsonProperty("Items2")] public List> Items2 { get; set; } = []; + [JsonProperty("Items3")] public List> Items3 { get; set; } = []; + [JsonProperty("Items4")] public List> Items4 { get; set; } = []; + [JsonProperty("Items5")] public List> Items5 { get; set; } = []; + [JsonProperty("Items6")] public List> Items6 { get; set; } = []; + + public List> GetItems(uint breakLevel) => breakLevel switch + { + 1 => Items1, + 2 => Items2, + 3 => Items3, + 4 => Items4, + 5 => Items5, + 6 => Items6, + _ => [] + }; + + public override uint GetId() => (uint)Id; + + public override void Loaded() + { + GameData.BreakData[Id] = this; + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index fabd1fe..80d94b3 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -16,6 +16,7 @@ public static class GameData public static Dictionary ArItemData { get; private set; } = []; public static Dictionary ManifestationData { get; private set; } = []; public static Dictionary Rogue3DDifficultData { get; private set; } = []; + public static Dictionary BreakData { get; private set; } = []; public static Dictionary SpineData { get; private set; } = []; public static Dictionary NodeConditionData { get; private set; } = []; public static List SupportCardData { get; private set; } = []; diff --git a/GameServer/Server/CallGS/Handlers/Weapon/Weapon_Break.cs b/GameServer/Server/CallGS/Handlers/Weapon/Weapon_Break.cs new file mode 100644 index 0000000..96c2996 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Weapon/Weapon_Break.cs @@ -0,0 +1,98 @@ +using MikuSB.Data; +using MikuSB.Database; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Weapon; + +// s2c: function(sErr) — send "null" on success (json.decode("null") = nil = falsy in Lua) +[CallGSApi("Weapon_Break")] +public class Weapon_Break : ICallGSHandler +{ + private const uint MaxBreak = 6; + + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null || req.WeaponId == 0) + { + await CallGSRouter.SendScript(connection, "Weapon_Break", "\"error.BadParam\""); + return; + } + + var weapon = player.InventoryManager.InventoryData.Weapons.GetValueOrDefault((uint)req.WeaponId); + if (weapon == null) + { + await CallGSRouter.SendScript(connection, "Weapon_Break", "\"error.BadParam\""); + return; + } + + if (weapon.Break >= MaxBreak) + { + await CallGSRouter.SendScript(connection, "Weapon_Break", "\"tip.already_max_break\""); + return; + } + + var nextBreak = weapon.Break + 1; + + // Look up break cost from WeaponExcel → BreakExcel + var weaponExcel = GameData.WeaponData.Values.FirstOrDefault(x => + GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level) == weapon.TemplateId); + + var requestedMaterials = new Dictionary(); + if (weaponExcel != null && GameData.BreakData.TryGetValue(weaponExcel.BreakMatID, out var breakExcel)) + { + foreach (var row in breakExcel.GetItems(nextBreak)) + { + if (row.Count < 5) continue; + var tid = GameResourceTemplateId.FromGdpl( + (uint)row[0], (uint)row[1], (uint)row[2], (uint)row[3]); + requestedMaterials[tid] = requestedMaterials.GetValueOrDefault(tid) + (uint)row[4]; + } + } + + // Validate materials + foreach (var (tid, count) in requestedMaterials) + { + var item = player.InventoryManager.InventoryData.Items.Values.FirstOrDefault(x => x.TemplateId == tid); + if (item == null || item.ItemCount < count) + { + await CallGSRouter.SendScript(connection, "Weapon_Break", "\"tip.not_material_for_break\""); + return; + } + } + + // Consume materials + var syncItems = new List(); + foreach (var (tid, count) in requestedMaterials) + { + var item = player.InventoryManager.InventoryData.Items.Values.First(x => x.TemplateId == tid); + item.ItemCount -= count; + var proto = item.ToProto(); + if (item.ItemCount == 0) + { + player.InventoryManager.InventoryData.Items.Remove(item.UniqueId); + proto.Count = 0; + } + syncItems.Add(proto); + } + + weapon.Break = nextBreak; + syncItems.Add(weapon.ToProto()); + + DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData); + + var sync = new NtfSyncPlayer(); + sync.Items.AddRange(syncItems); + + await CallGSRouter.SendScript(connection, "Weapon_Break", "null", sync); + } +} + +internal sealed class WeaponBreakParam +{ + [JsonPropertyName("Id")] + public int WeaponId { get; set; } +} From 846139347acbecada4db93c81069ed3db042a04d Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 29 Apr 2026 09:58:35 +0900 Subject: [PATCH 14/22] Implement Weapon_Evolution --- Common/Database/Inventory/InventoryData.cs | 4 +- .../Handlers/Weapon/Weapon_Evolution.cs | 77 +++++++++++++++++++ version.txt | 2 +- 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 GameServer/Server/CallGS/Handlers/Weapon/Weapon_Evolution.cs diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index db9888c..02cbf50 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -56,6 +56,7 @@ public abstract class GrowableItemInfo : BaseGameItemInfo public new uint Level { get; set; } public new uint Exp { get; set; } public uint Break { get; set; } + public uint Evolue { get; set; } public uint EquipAvatarId { get; set; } } @@ -73,7 +74,8 @@ public class GameWeaponInfo : GrowableItemInfo { Level = Level, Exp = Exp, - Break = Break + Break = Break, + Evolue = Evolue } }; return proto; diff --git a/GameServer/Server/CallGS/Handlers/Weapon/Weapon_Evolution.cs b/GameServer/Server/CallGS/Handlers/Weapon/Weapon_Evolution.cs new file mode 100644 index 0000000..bf8f9f1 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Weapon/Weapon_Evolution.cs @@ -0,0 +1,77 @@ +using MikuSB.Database; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Weapon; + +// s2c: function(sErr) — send "null" on success +// Id = target weapon UniqueId +// nItemId = material item UniqueId (weapon or supply item to consume) +[CallGSApi("Weapon_Evolution")] +public class Weapon_Evolution : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null || req.WeaponId == 0 || req.MaterialId == 0) + { + await CallGSRouter.SendScript(connection, "Weapon_Evolution", "\"error.BadParam\""); + return; + } + + var weapon = player.InventoryManager.InventoryData.Weapons.GetValueOrDefault((uint)req.WeaponId); + if (weapon == null) + { + await CallGSRouter.SendScript(connection, "Weapon_Evolution", "\"error.BadParam\""); + return; + } + + var syncItems = new List(); + + // Material can be a weapon or a regular item + if (player.InventoryManager.InventoryData.Weapons.TryGetValue((uint)req.MaterialId, out var matWeapon)) + { + player.InventoryManager.InventoryData.Weapons.Remove((uint)req.MaterialId); + var removed = matWeapon.ToProto(); + removed.Count = 0; + syncItems.Add(removed); + } + else if (player.InventoryManager.InventoryData.Items.TryGetValue((uint)req.MaterialId, out var matItem)) + { + matItem.ItemCount--; + var proto = matItem.ToProto(); + if (matItem.ItemCount == 0) + { + player.InventoryManager.InventoryData.Items.Remove(matItem.UniqueId); + proto.Count = 0; + } + syncItems.Add(proto); + } + else + { + await CallGSRouter.SendScript(connection, "Weapon_Evolution", "\"tip.not_material\""); + return; + } + + weapon.Evolue++; + syncItems.Add(weapon.ToProto()); + + DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData); + + var sync = new NtfSyncPlayer(); + sync.Items.AddRange(syncItems); + + await CallGSRouter.SendScript(connection, "Weapon_Evolution", "null", sync); + } +} + +internal sealed class WeaponEvolutionParam +{ + [JsonPropertyName("Id")] + public int WeaponId { get; set; } + + [JsonPropertyName("nItemId")] + public int MaterialId { get; set; } +} diff --git a/version.txt b/version.txt index ae6845b..45e88f8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v=1.3 \ No newline at end of file +v=1.4 \ No newline at end of file From e32319aa504cb2c9cf5053f988e2ebfeb2352cd3 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:22:35 +0800 Subject: [PATCH 15/22] fix black screen after clicking love icon --- .../Server/CallGS/Handlers/Girl/EnterGirlRoom.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/GameServer/Server/CallGS/Handlers/Girl/EnterGirlRoom.cs b/GameServer/Server/CallGS/Handlers/Girl/EnterGirlRoom.cs index d2101d5..5a476ef 100644 --- a/GameServer/Server/CallGS/Handlers/Girl/EnterGirlRoom.cs +++ b/GameServer/Server/CallGS/Handlers/Girl/EnterGirlRoom.cs @@ -12,11 +12,19 @@ public class EnterGirlRoom : ICallGSHandler var req = JsonSerializer.Deserialize(param); var response = new JsonObject { - ["nCardId"] = req?.CardId ?? 1, - ["nSkinId"] = req?.SkinId ?? 0, - ["bOpen"] = true + ["nCardId"] = 0, + ["nSkinId"] = 0, + ["bOpen"] = false }; + if (req == null) + { + await CallGSRouter.SendScript(connection, "EnterGirlRoom", response.ToJsonString()); + return; + } + response["nCardId"] = req.CardId; + response["nSkinId"] = req.SkinId; + response["bOpen"] = true; await CallGSRouter.SendScript(connection, "EnterGirlRoom", response.ToJsonString()); } } @@ -26,6 +34,6 @@ internal sealed class EnterGirlRoomParam [JsonPropertyName("nSkinId")] public int SkinId { get; set; } - [JsonPropertyName("nCardID")] + [JsonPropertyName("nCardId")] public uint CardId { get; set; } } \ No newline at end of file From 1969c1ec897302fcb4afe0fb9362444af506b271 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:46:21 +0800 Subject: [PATCH 16/22] use enum for item slots --- Common/Database/Character/CharacterData.cs | 9 ++-- Common/Database/Inventory/InventoryData.cs | 4 +- Common/Enums/Item/ItemFlagEnum.cs | 13 ------ Common/Enums/Item/ItemTypeEnum.cs | 52 ++++++++++++++++++++++ 4 files changed, 58 insertions(+), 20 deletions(-) delete mode 100644 Common/Enums/Item/ItemFlagEnum.cs diff --git a/Common/Database/Character/CharacterData.cs b/Common/Database/Character/CharacterData.cs index 2330f96..ec0608a 100644 --- a/Common/Database/Character/CharacterData.cs +++ b/Common/Database/Character/CharacterData.cs @@ -29,8 +29,7 @@ public class CharacterInfo [SugarColumn(IsJson = true)] public List UnlockedSkin { get; set; } = []; [SugarColumn(IsJson = true)] public List Spines { get; set; } = []; [SugarColumn(IsJson = true)] public List Affixs { get; set; } = []; - // Key = EqSlot (= support card Detail), Value = support card UniqueId - [SugarColumn(IsJson = true)] public Dictionary SupportSlots { get; set; } = []; + [SugarColumn(IsJson = true)] public Dictionary SupportSlots { get; set; } = []; // Key = EqSlot (= support card Detail), Value = support card UniqueId public long Timestamp { get; set; } public uint Count { get; set; } = 1; @@ -56,9 +55,9 @@ public class CharacterInfo proto.Enhance.Spines.AddRange(Spines.Select(x => (ulong)x)); proto.Enhance.Affixs.AddRange(Affixs); - proto.Slots[4] = WeaponUniqueId; - proto.Slots[5] = SkinId; - proto.Slots[6] = WeaponSkinId; + proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_WEAPON] = WeaponUniqueId; + proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_SKIN] = SkinId; + proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_WEAPON_SKIN] = WeaponSkinId; foreach (var (slot, uid) in SupportSlots) proto.Slots[slot] = uid; diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index 02cbf50..38650ca 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -93,7 +93,7 @@ public class GameSkinInfo : BaseGameItemInfo Count = ItemCount, Flag = (uint)Flag, }; - proto.Slots[11] = Math.Min(SkinType, 1); + proto.Slots[(uint)ItemSkinSlotTypeEnum.SLOT_CARD_SKIL_TYPE] = Math.Min(SkinType, 1); return proto; } } @@ -116,7 +116,7 @@ public class GameSupportCardInfo : BaseGameItemInfo Exp = Exp } }; - proto.Slots[1] = AffixId; + proto.Slots[(uint)ItemSupportCardSlotTypeEnum.SLOT_AFFIXINDEX] = AffixId; return proto; } } diff --git a/Common/Enums/Item/ItemFlagEnum.cs b/Common/Enums/Item/ItemFlagEnum.cs deleted file mode 100644 index 8686b8d..0000000 --- a/Common/Enums/Item/ItemFlagEnum.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MikuSB.Enums.Item; - -public enum ItemFlagEnum -{ - FLAG_USE = 1,// 使用中 - FLAG_LOCK = 2,// 锁定中 - FLAG_READED = 4,// 道具已查看 - FLAG_LEAVE = 8,// 角色大招后离场 - FLAG_WEAPON_DEFAULT = 16,// 武器显示原始样式 - FLAG_WEAPON_AUDIO = 32,// 武器消音器音效 - FLAG_ROLE_LIKE = 64,// 心选角色 -} - diff --git a/Common/Enums/Item/ItemTypeEnum.cs b/Common/Enums/Item/ItemTypeEnum.cs index 8e7be17..273cdc3 100644 --- a/Common/Enums/Item/ItemTypeEnum.cs +++ b/Common/Enums/Item/ItemTypeEnum.cs @@ -25,4 +25,56 @@ public enum ItemTypeEnum TYPE_MAIN_SCENE = 21, //主界面场景道具 TYPE_AR = 24, //AR道具 TYPE_CALL = 25, //电话陪伴道具 +} + +public enum ItemCardSlotTypeEnum +{ + SLOT_SUPPORTERCARD1 = 1, // 后勤卡 + SLOT_SUPPORTERCARD2 = 2, // 后勤卡 + SLOT_SUPPORTERCARD3 = 3, // 后勤卡 + SLOT_WEAPON = 4, // 武器 + SLOT_SKIN = 5, // 时装 + SLOT_WEAPON_SKIN = 6, // 武器时装 + SLOT_SUPPORTERINDEX = 7, // 当前使用的后勤组 + SLOT_SUPPORTERCARD4 = 8, // 后勤卡 + SLOT_SUPPORTERCARD5 = 9, // 后勤卡 + SLOT_SUPPORTERCARD6 = 10, // 后勤卡 + SLOT_SUPPORTERCARD7 = 11, // 后勤卡 + SLOT_SUPPORTERCARD8 = 12, // 后勤卡 + SLOT_SUPPORTERCARD9 = 13, // 后勤卡 +} + +public enum ItemSkinPartSlotTypeEnum +{ + SLOT_SkinPartSlot1 = 1, + SLOT_SkinPartSlot2 = 2, + SLOT_SkinPartSlot3 = 3, + SLOT_SkinPartSlot4 = 4, + SLOT_SkinPartSlot5 = 5, + SLOT_SkinPartSlot6 = 6, + SLOT_SkinPartSlot7 = 7, + SLOT_SkinPartSlot8 = 8, + SLOT_SkinPartSlot9 = 9, + SLOT_SkinPartSlot10 = 10, +} + +public enum ItemSkinSlotTypeEnum +{ + SLOT_CARD_SKIL_TYPE = 11 +} + +public enum ItemSupportCardSlotTypeEnum +{ + SLOT_AFFIXINDEX = 1 // 可洗练的初始词缀索引 +} + +public enum ItemFlagEnum +{ + FLAG_USE = 1, // 使用中 + FLAG_LOCK = 2, // 锁定中 + FLAG_READED = 4, // 道具已查看 + FLAG_LEAVE = 8, // 角色大招后离场 + FLAG_WEAPON_DEFAULT = 16, // 武器显示原始样式 + FLAG_WEAPON_AUDIO = 32, // 武器消音器音效 + FLAG_ROLE_LIKE = 64, // 心选角色 } \ No newline at end of file From 9b8fa5d7c8cd41a9e4ce89923783716789623eec Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:02:11 +0800 Subject: [PATCH 17/22] add profile item --- Common/Data/Excel/ProfileExcel.cs | 22 +++++++++ Common/Data/GameData.cs | 2 +- Common/Enums/Player/ProfileTypeEnum.cs | 24 +++++++++ .../Message/LanguageCHS.cs | 4 +- .../Message/LanguageCHT.cs | 4 +- .../Message/LanguageEN.cs | 4 +- GameServer/Command/Commands/CommandGiveAll.cs | 38 +++++++++++++- GameServer/Game/Inventory/InventoryManager.cs | 33 ++++++++++--- .../Handlers/Daily/Daily_SetSelectSuit.cs | 2 +- .../Handlers/Girl/GirlWeaponSkin_Change.cs | 4 +- .../Misc/PlayerSetting_ChangeShowCard.cs | 10 +--- .../Misc/PlayerSetting_SetProfileFace.cs | 49 +++++++++++++++++++ .../Misc/PlayerSetting_SetShowBubble.cs | 36 ++++++++++++++ .../Misc/PlayerSetting_SetShowCover.cs | 36 ++++++++++++++ .../Misc/PlayerSetting_SetShowNameCard.cs | 36 ++++++++++++++ 15 files changed, 282 insertions(+), 22 deletions(-) create mode 100644 Common/Data/Excel/ProfileExcel.cs create mode 100644 Common/Enums/Player/ProfileTypeEnum.cs create mode 100644 GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetProfileFace.cs create mode 100644 GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBubble.cs create mode 100644 GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowCover.cs create mode 100644 GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowNameCard.cs diff --git a/Common/Data/Excel/ProfileExcel.cs b/Common/Data/Excel/ProfileExcel.cs new file mode 100644 index 0000000..8084e13 --- /dev/null +++ b/Common/Data/Excel/ProfileExcel.cs @@ -0,0 +1,22 @@ +namespace MikuSB.Data.Excel; + +[ResourceEntity("profile.json")] +public class ProfileExcel : ExcelResource +{ + public uint Genre { get; set; } + public uint Detail { get; set; } + public uint Particular { get; set; } + public uint Level { get; set; } + public string I18n { get; set; } = ""; + public string LuaType { get; set; } = ""; + + public override uint GetId() + { + return (uint)I18n.GetHashCode(); + } + + public override void Loaded() + { + GameData.ProfileData.Add(GetId(), this); + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 80d94b3..0a23bd9 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -22,7 +22,7 @@ public static class GameData public static List SupportCardData { get; private set; } = []; public static Dictionary WeaponSkinData { get; private set; } = []; public static Dictionary DailyLevelData { get; private set; } = []; - + public static Dictionary ProfileData { get; private set; } = []; } public static class GameResourceTemplateId diff --git a/Common/Enums/Player/ProfileTypeEnum.cs b/Common/Enums/Player/ProfileTypeEnum.cs new file mode 100644 index 0000000..bed54fa --- /dev/null +++ b/Common/Enums/Player/ProfileTypeEnum.cs @@ -0,0 +1,24 @@ +namespace MikuSB.Enums.Player; + +public enum ProfileShowItemTypeEnum +{ + SHOWITEM_CARD1 = 1, //第1个展示卡 + SHOWITEM_CARD2 = 2, //第2个展示卡 + SHOWITEM_CARD3 = 3, //第3个展示卡 + SHOWITEM_GIRL = 4, //看板娘 + SHOWITEM_FACE = 5, //头像 + SHOWITEM_FRAME = 6, //头像框 + SHOWITEM_CARD4 = 7, //第4个展示卡 + SHOWITEM_CARD5 = 8, //第5个展示卡 + SHOWITEM_SKIN1 = 9, //第1个展示皮肤 + SHOWITEM_SKIN2 = 10, //第2个展示皮肤 + SHOWITEM_SKIN3 = 11, //第3个展示皮肤 + SHOWITEM_SKIN4 = 12, //第4个展示皮肤 + SHOWITEM_SKIN5 = 13, //第5个展示皮肤 + SHOWITEM_BADGE1 = 14, //第一个展示勋章 + SHOWITEM_BADGE2 = 15, //第二个展示勋章 + SHOWITEM_BADGE3 = 16, //第三个展示勋章 + SHOWITEM_COVER = 17, //展示封面 + SHOWITEM_NAMECARD = 18, //展示名片 + SHOWITEM_BUBBLE = 19, //聊天气泡 +} \ No newline at end of file diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 3c9fd14..48c5a50 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -35,6 +35,7 @@ public class ServerTextCHS /// public class WordTextCHS { + public string Profile => "个人资料"; public string WeaponSkin => "武器皮肤"; public string SupportCard => "支援卡"; public string Weapon => "武器"; @@ -240,7 +241,8 @@ public class GiveAllTextCHS "注意:-1 表示全部"; public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + "用法:/giveall weaponskin -p<特定>\n" + - "用法:/giveall card -p<特定> -l<等級>"; + "用法:/giveall card -p<特定> -l<等級>" + + "用法:/giveall profile -g<类型> -p<特定> -l<等级>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 个 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index 03d7eb1..e025fb6 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -35,6 +35,7 @@ public class ServerTextCHT /// public class WordTextCHT { + public string Profile => "個人資料"; public string WeaponSkin => "武器外觀"; public string SupportCard => "支援卡"; public string Weapon => "武器"; @@ -240,7 +241,8 @@ public class GiveAllTextCHT "注意:-1 表示全部"; public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + "用法:/giveall weaponskin -p<特定>\n" + - "用法:/giveall card -p<特定> -l<等級>"; + "用法:/giveall card -p<特定> -l<等級>" + + "用法:/giveall profile -g<類型> -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 個 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index 302cc30..a0121b6 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -35,6 +35,7 @@ public class ServerTextEN /// public class WordTextEN { + public string Profile => "Profile"; public string WeaponSkin => "Weapon Skin"; public string Valk => "Valkyrie"; public string Material => "Material"; @@ -206,7 +207,8 @@ public class GiveAllTextEN "Note: -1 means all"; public string Usage => "Usage: /giveall weapon -p -l\n" + "Usage: /giveall weaponskin -p\n" + - "Usage: /giveall card -p -l"; + "Usage: /giveall card -p -l" + + "Usage: /giveall profile -g -p -l"; public string NotFound => "{0} not found!"; public string GiveAllItems => "Added {0} {1} to player!"; } diff --git a/GameServer/Command/Commands/CommandGiveAll.cs b/GameServer/Command/Commands/CommandGiveAll.cs index fb4a8bd..65977d3 100644 --- a/GameServer/Command/Commands/CommandGiveAll.cs +++ b/GameServer/Command/Commands/CommandGiveAll.cs @@ -96,7 +96,7 @@ public class CommandGiveAll : ICommands foreach (var config in GameData.WeaponSkinData.Values) { var weaponSkin = await player.InventoryManager! - .AddWeaponSkinItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, 1, false); + .AddWeaponSkinItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, config.Level, false); if (weaponSkin != null) weaponSkins.Add(weaponSkin); } } @@ -114,4 +114,40 @@ public class CommandGiveAll : ICommands await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", I18NManager.Translate("Word.WeaponSkin"), weaponSkins.Count.ToString())); } + + [CommandMethod("profile")] + public async ValueTask GiveAllProfile(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (await arg.GetOption('p') is not int particular) return; + if (await arg.GetOption('l') is not int level) return; + if (await arg.GetOption('g') is not int genre) return; + + var detail = arg.GetInt(0); + var player = arg.Target!.Player!; + List profileItems = []; + if (detail == -1) + { + // add all + foreach (var config in GameData.ProfileData.Values) + { + var profile = await player.InventoryManager! + .AddProfileItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, config.Level, false); + if (profile != null) profileItems.Add(profile); + } + } + else + { + var profile = await player.InventoryManager!.AddProfileItem((ItemTypeEnum)genre, (uint)detail, (uint)particular, (uint)level, false); + if (profile == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.Profile"))); + return; + } + profileItems.Add(profile); + } + if (profileItems.Count > 0) await player.SendPacket(new PacketNtfCallScript(profileItems)); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", + I18NManager.Translate("Word.Profile"), profileItems.Count.ToString())); + } } \ No newline at end of file diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index fc9b088..f83d094 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -25,7 +25,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) UniqueId = InventoryData.NextUniqueUid++, Level = weaponLevel, Break = GetWeaponBreak(weaponLevel), - ItemType = ItemTypeEnum.TYPE_WEAPON, + ItemType = genre, ItemCount = 1 }; InventoryData.Weapons[weaponInfo.UniqueId] = weaponInfo; @@ -67,13 +67,13 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) if (genre != ItemTypeEnum.TYPE_CARD_SKIN) return null; var skinData = GameData.CardSkinData.Values.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level); if (skinData == null) return null; - var templateId = GameResourceTemplateId.FromGdpl((uint)genre,detail,particular,level); + if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null; var skinInfo = new GameSkinInfo { TemplateId = templateId, UniqueId = InventoryData.NextUniqueUid++, - ItemType = ItemTypeEnum.TYPE_CARD_SKIN, + ItemType = genre, ItemCount = 1 }; InventoryData.Skins[skinInfo.UniqueId] = skinInfo; @@ -111,7 +111,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) { TemplateId = templateId, UniqueId = InventoryData.NextUniqueUid++, - ItemType = ItemTypeEnum.TYPE_AR, + ItemType = genre, ItemCount = 1 }; InventoryData.Items[arInfo.UniqueId] = arInfo; @@ -171,7 +171,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) { TemplateId = templateId, UniqueId = InventoryData.NextUniqueUid++, - ItemType = ItemTypeEnum.TYPE_MANIFESTATION, + ItemType = genre, ItemCount = 1 }; InventoryData.Items[manifestInfo.UniqueId] = manifestInfo; @@ -240,7 +240,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) { TemplateId = templateId, UniqueId = InventoryData.NextUniqueUid++, - ItemType = ItemTypeEnum.TYPE_WEAPON_SKIN, + ItemType = genre, ItemCount = 1 }; InventoryData.Items[skinInfo.UniqueId] = skinInfo; @@ -249,4 +249,25 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) return skinInfo; } + + public async ValueTask AddProfileItem(ItemTypeEnum genre, uint detail, uint particular, uint level = 1, bool sendPacket = true) + { + if (genre < ItemTypeEnum.TYPE_PROFILE || genre > ItemTypeEnum.TYPE_ANALYST) return null; + var profileData = GameData.ProfileData.Values.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level); + if (profileData == null) return null; + var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level); + if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null; + var profileInfo = new BaseGameItemInfo + { + TemplateId = templateId, + UniqueId = InventoryData.NextUniqueUid++, + ItemType = genre, + ItemCount = 1 + }; + InventoryData.Items[profileInfo.UniqueId] = profileInfo; + + if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([profileInfo])); + + return profileInfo; + } } \ No newline at end of file diff --git a/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs b/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs index 2d51857..d233aba 100644 --- a/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs +++ b/GameServer/Server/CallGS/Handlers/Daily/Daily_SetSelectSuit.cs @@ -11,7 +11,7 @@ public class Daily_SetSelectSuit : ICallGSHandler var req = JsonSerializer.Deserialize(param); if (req == null) { - await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}"); + await CallGSRouter.SendScript(connection, "Daily_SetSelectSuit", "{}"); return; } var rsp = $"{{\"SuitId\":{req.Suit}}}"; diff --git a/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs b/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs index 01708fe..14bac71 100644 --- a/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs +++ b/GameServer/Server/CallGS/Handlers/Girl/GirlWeaponSkin_Change.cs @@ -13,7 +13,7 @@ public class GirlWeaponSkin_Change : ICallGSHandler var req = JsonSerializer.Deserialize(param); if (req == null) { - await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}"); + await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{\"err\":\"error.BadParam\"}"); return; } @@ -33,7 +33,7 @@ public class GirlWeaponSkin_Change : ICallGSHandler Items = { cardData.ToProto() } }; - await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}", sync); + await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "null", sync); } } diff --git a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_ChangeShowCard.cs b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_ChangeShowCard.cs index 1c2957f..4347bbb 100644 --- a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_ChangeShowCard.cs +++ b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_ChangeShowCard.cs @@ -1,4 +1,4 @@ -using MikuSB.Database; +using MikuSB.Enums.Player; using MikuSB.Proto; using System.Text.Json; using System.Text.Json.Serialization; @@ -8,8 +8,6 @@ namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; [CallGSApi("PlayerSetting_ChangeShowCard")] public class PlayerSetting_ChangeShowCard : ICallGSHandler { - private const int ShowItemGirlIndex = 4; - public async Task Handle(Connection connection, string param, ushort seqNo) { var player = connection.Player!; @@ -23,13 +21,9 @@ public class PlayerSetting_ChangeShowCard : ICallGSHandler await CallGSRouter.SendScript(connection, "PlayerSetting_ChangeShowCard", "{}"); return; } - - player.SetShowItem(ShowItemGirlIndex, card.Guid); - DatabaseHelper.SaveDatabaseType(player.Data); - + player.SetShowItem((int)ProfileShowItemTypeEnum.SHOWITEM_GIRL, card.Guid); var sync = new NtfSyncPlayer(); sync.ShowItems.AddRange(player.Data.ShowItems); - await CallGSRouter.SendScript(connection, "PlayerSetting_ChangeShowCard", "{}", sync); } } diff --git a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetProfileFace.cs b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetProfileFace.cs new file mode 100644 index 0000000..ed50134 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetProfileFace.cs @@ -0,0 +1,49 @@ +using MikuSB.Enums.Player; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; + +[CallGSApi("PlayerSetting_SetProfileFace")] +public class PlayerSetting_SetProfileFace : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null) + return; + + if (req.HeadItemId > 0) + { + var item = player.InventoryManager.GetNormalItem(req.HeadItemId); + if (item == null) + { + await CallGSRouter.SendScript(connection, "PlayerSetting_SetProfileFace", "{\"err\":\"error.BadParam\"}"); + return; + } + player.SetShowItem((int)ProfileShowItemTypeEnum.SHOWITEM_FACE, item.UniqueId); + } + if (req.FrameItemId > 0) + { + var item = player.InventoryManager.GetNormalItem(req.FrameItemId); + if (item == null) + { + await CallGSRouter.SendScript(connection, "PlayerSetting_SetProfileFace", "{\"err\":\"error.BadParam\"}"); + return; + } + player.SetShowItem((int)ProfileShowItemTypeEnum.SHOWITEM_FRAME, item.UniqueId); + } + + var sync = new NtfSyncPlayer(); + sync.ShowItems.AddRange(player.Data.ShowItems); + await CallGSRouter.SendScript(connection, "PlayerSetting_SetProfileFace", "null", sync); + } +} + +internal sealed class SetProfileFaceParam +{ + [JsonPropertyName("nHeadItemID")] public uint HeadItemId { get; set; } + [JsonPropertyName("nFrameItemID")] public uint FrameItemId { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBubble.cs b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBubble.cs new file mode 100644 index 0000000..5922f5c --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBubble.cs @@ -0,0 +1,36 @@ +using MikuSB.Enums.Player; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; + +[CallGSApi("PlayerSetting_SetShowBubble")] +public class PlayerSetting_SetShowBubble : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null) + return; + + var item = player.InventoryManager.GetNormalItem(req.Id); + if (item == null) + { + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowBubble", "{\"err\":\"error.BadParam\"}"); + return; + } + + player.SetShowItem((int)ProfileShowItemTypeEnum.SHOWITEM_BUBBLE, item.UniqueId); + var sync = new NtfSyncPlayer(); + sync.ShowItems.AddRange(player.Data.ShowItems); + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowBubble", "null", sync); + } +} + +internal sealed class SetShowBubbleParam +{ + [JsonPropertyName("nID")] + public uint Id { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowCover.cs b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowCover.cs new file mode 100644 index 0000000..c1b7315 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowCover.cs @@ -0,0 +1,36 @@ +using MikuSB.Enums.Player; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; + +[CallGSApi("PlayerSetting_SetShowCover")] +public class PlayerSetting_SetShowCover : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null) + return; + + var item = player.InventoryManager.GetNormalItem(req.Id); + if (item == null) + { + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowCover", "{\"err\":\"error.BadParam\"}"); + return; + } + + player.SetShowItem((int)ProfileShowItemTypeEnum.SHOWITEM_COVER, item.UniqueId); + var sync = new NtfSyncPlayer(); + sync.ShowItems.AddRange(player.Data.ShowItems); + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowCover", "null", sync); + } +} + +internal sealed class SetShowCoverParam +{ + [JsonPropertyName("nID")] + public uint Id { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowNameCard.cs b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowNameCard.cs new file mode 100644 index 0000000..e1846ff --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowNameCard.cs @@ -0,0 +1,36 @@ +using MikuSB.Enums.Player; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; + +[CallGSApi("PlayerSetting_SetShowNameCard")] +public class PlayerSetting_SetShowNameCard : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null) + return; + + var item = player.InventoryManager.GetNormalItem(req.Id); + if (item == null) + { + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowNameCard", "{\"err\":\"error.BadParam\"}"); + return; + } + + player.SetShowItem((int)ProfileShowItemTypeEnum.SHOWITEM_NAMECARD, item.UniqueId); + var sync = new NtfSyncPlayer(); + sync.ShowItems.AddRange(player.Data.ShowItems); + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowNameCard", "null", sync); + } +} + +internal sealed class SetShowNameCardParam +{ + [JsonPropertyName("nID")] + public uint Id { get; set; } +} From 5f173ce8d2d41979f55ca424e8ccab7fe7a5fd48 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:41:45 +0800 Subject: [PATCH 18/22] add badge --- GameServer/Game/Player/PlayerInstance.cs | 1 + .../Misc/PlayerSetting_SetShowBadge.cs | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBadge.cs diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index a424c18..1b77b98 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -210,6 +210,7 @@ public class PlayerInstance(PlayerGameData data) Sex = Data.Gender, Vigor = Data.Vigor, Solutions = { LineupManager.LineupData.LineupInfo.Values.Select(x => x.ToProto()) }, + Badges = { InventoryManager.InventoryData.Items.Values.Where(x => x.ItemType == ItemTypeEnum.TYPE_BADGE).Select(x => (ulong)x.UniqueId) } }; foreach (var chara in CharacterManager.CharacterData.Characters) proto.Items.Add(chara.ToProto()); diff --git a/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBadge.cs b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBadge.cs new file mode 100644 index 0000000..4dab8bb --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Misc/PlayerSetting_SetShowBadge.cs @@ -0,0 +1,43 @@ +using MikuSB.Enums.Player; +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Misc; + +[CallGSApi("PlayerSetting_SetShowBadge")] +public class PlayerSetting_SetShowBadge : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + var req = JsonSerializer.Deserialize(param); + if (req == null) + { + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowBadge", "{\"err\":\"error.BadParam\"}"); + return; + } + + var slots = new[] + { + ProfileShowItemTypeEnum.SHOWITEM_BADGE1, + ProfileShowItemTypeEnum.SHOWITEM_BADGE2, + ProfileShowItemTypeEnum.SHOWITEM_BADGE3 + }; + for (int i = 0; i < slots.Length; i++) + { + var uniqueId = i < req.Badges.Count ? req.Badges[i] : 0; + player.SetShowItem((int)slots[i], uniqueId); + } + + var sync = new NtfSyncPlayer(); + sync.ShowItems.AddRange(player.Data.ShowItems); + await CallGSRouter.SendScript(connection, "PlayerSetting_SetShowBadge", "null", sync); + } +} + +internal sealed class SetShowBadgeParam +{ + [JsonPropertyName("tbBadge")] + public List Badges { get; set; } = []; +} From e8bb77e90ea0cf8ccf26815591bf7286c51d17c7 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:04:17 +0800 Subject: [PATCH 19/22] logistic index --- Common/Database/Character/CharacterData.cs | 2 + .../Girl/RoleCard_SetSupporterTeamIndex.cs | 37 +++++++++++++++++++ .../SupporterCard/SupporterCard_Equip.cs | 11 +++++- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 GameServer/Server/CallGS/Handlers/Girl/RoleCard_SetSupporterTeamIndex.cs diff --git a/Common/Database/Character/CharacterData.cs b/Common/Database/Character/CharacterData.cs index ec0608a..6eb42e0 100644 --- a/Common/Database/Character/CharacterData.cs +++ b/Common/Database/Character/CharacterData.cs @@ -24,6 +24,7 @@ public class CharacterInfo public uint WeaponUniqueId { get; set; } public uint SkinId { get; set; } public uint WeaponSkinId { get; set; } + public uint SupportTeamIndex { get; set; } = 1; public ItemFlagEnum Flag { get; set; } = ItemFlagEnum.FLAG_READED; public uint Expiration { get; set; } [SugarColumn(IsJson = true)] public List UnlockedSkin { get; set; } = []; @@ -58,6 +59,7 @@ public class CharacterInfo proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_WEAPON] = WeaponUniqueId; proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_SKIN] = SkinId; proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_WEAPON_SKIN] = WeaponSkinId; + proto.Slots[(uint)ItemCardSlotTypeEnum.SLOT_SUPPORTERINDEX] = SupportTeamIndex; foreach (var (slot, uid) in SupportSlots) proto.Slots[slot] = uid; diff --git a/GameServer/Server/CallGS/Handlers/Girl/RoleCard_SetSupporterTeamIndex.cs b/GameServer/Server/CallGS/Handlers/Girl/RoleCard_SetSupporterTeamIndex.cs new file mode 100644 index 0000000..e16bc28 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Girl/RoleCard_SetSupporterTeamIndex.cs @@ -0,0 +1,37 @@ +using MikuSB.Proto; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Girl; + +[CallGSApi("RoleCard_SetSupporterTeamIndex")] +public class RoleCard_SetSupporterTeamIndex : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null) + { + await CallGSRouter.SendScript(connection, "RoleCard_SetSupporterTeamIndex", "{\"err\":\"error.BadParam\"}"); + return; + } + var player = connection.Player!; + var cardData = player.CharacterManager.GetCharacterByGUID(req.CardId); + if (cardData == null) return; + + cardData.SupportTeamIndex = req.Index; + var sync = new NtfSyncPlayer + { + Items = { cardData.ToProto() } + }; + await CallGSRouter.SendScript(connection, "RoleCard_SetSupporterTeamIndex", "null", sync); + } +} + +internal sealed class SetSupporterTeamIndexParam +{ + [JsonPropertyName("Id")] + public uint CardId { get; set; } + public uint Index { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Equip.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Equip.cs index 25277d1..30d0a00 100644 --- a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Equip.cs +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Equip.cs @@ -25,7 +25,8 @@ public class SupporterCard_Equip : ICallGSHandler return; } - var slot = (uint)req.EqSlot; + var teamIndex = card.SupportTeamIndex; + var slot = GetTeamIndex((uint)req.EqSlot, teamIndex); // If an existing card is equipped in this slot and bForce is false, ask for confirmation if (!req.Force && req.CurrentEquippedUid != 0 && card.SupportSlots.TryGetValue(slot, out var existing) && existing != 0) @@ -46,6 +47,14 @@ public class SupporterCard_Equip : ICallGSHandler var responseApi = string.IsNullOrEmpty(req.Model) ? "Logistics_Change" : "Logistics_Equip"; await CallGSRouter.SendScript(connection, responseApi, "{}", sync); } + + private uint GetTeamIndex(uint slot, uint teamIndex) + { + if (teamIndex == 1) return slot; + if (teamIndex == 2) return slot + 7; + if (teamIndex == 3) return slot + 10; + return slot; + } } internal sealed class SupporterCardEquipParam From 50121d619b1bcdf967e1b6f6a6690ce2463f7042 Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:42:25 +0800 Subject: [PATCH 20/22] add skin part --- Common/Data/Excel/CardSkinPartsExcel.cs | 27 +++++++++ Common/Data/GameData.cs | 1 + Common/Database/Inventory/InventoryData.cs | 2 + .../Message/LanguageCHS.cs | 4 +- .../Message/LanguageCHT.cs | 4 +- .../Message/LanguageEN.cs | 4 +- GameServer/Command/Commands/CommandGiveAll.cs | 36 ++++++++++++ GameServer/Game/Inventory/InventoryManager.cs | 21 +++++++ .../Handlers/Girl/GirlSkinParts_Update.cs | 57 +++++++++++++++++++ 9 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 Common/Data/Excel/CardSkinPartsExcel.cs create mode 100644 GameServer/Server/CallGS/Handlers/Girl/GirlSkinParts_Update.cs diff --git a/Common/Data/Excel/CardSkinPartsExcel.cs b/Common/Data/Excel/CardSkinPartsExcel.cs new file mode 100644 index 0000000..dd97684 --- /dev/null +++ b/Common/Data/Excel/CardSkinPartsExcel.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("card_skin_parts.json")] +public class CardSkinPartsExcel : ExcelResource +{ + public uint Genre { get; set; } + public uint Detail { get; set; } + public uint Particular { get; set; } + public uint Level { get; set; } + public uint Icon { get; set; } + public int AppearID { get; set; } + [JsonProperty("profile")] public List> CardSkinID { get; set; } = []; + public string I18n { get; set; } = ""; + [JsonIgnore] public ulong TemplateId { get; set; } + public override uint GetId() + { + return (uint)I18n.GetHashCode(); + } + + public override void Loaded() + { + TemplateId = GameResourceTemplateId.FromGdpl(Genre, Detail, Particular, Level); + GameData.CardSkinPartsData.Add(Icon, this); + } +} \ No newline at end of file diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 0a23bd9..b1d9454 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -23,6 +23,7 @@ public static class GameData public static Dictionary WeaponSkinData { get; private set; } = []; public static Dictionary DailyLevelData { get; private set; } = []; public static Dictionary ProfileData { get; private set; } = []; + public static Dictionary CardSkinPartsData { get; private set; } = []; } public static class GameResourceTemplateId diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index 38650ca..3d1f89e 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -83,6 +83,7 @@ public class GameWeaponInfo : GrowableItemInfo } public class GameSkinInfo : BaseGameItemInfo { + [SugarColumn(IsJson = true)] public Dictionary PartSlots { get; set; } = []; public uint SkinType { get; set; } public override Item ToProto() { @@ -94,6 +95,7 @@ public class GameSkinInfo : BaseGameItemInfo Flag = (uint)Flag, }; proto.Slots[(uint)ItemSkinSlotTypeEnum.SLOT_CARD_SKIL_TYPE] = Math.Min(SkinType, 1); + foreach (var (slot, uid) in PartSlots) proto.Slots[slot] = uid; return proto; } } diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 48c5a50..67821b0 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -35,6 +35,7 @@ public class ServerTextCHS /// public class WordTextCHS { + public string SkinPart => "皮肤部件"; public string Profile => "个人资料"; public string WeaponSkin => "武器皮肤"; public string SupportCard => "支援卡"; @@ -242,7 +243,8 @@ public class GiveAllTextCHS public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + "用法:/giveall weaponskin -p<特定>\n" + "用法:/giveall card -p<特定> -l<等級>" + - "用法:/giveall profile -g<类型> -p<特定> -l<等级>"; + "用法:/giveall profile -g<类型> -p<特定> -l<等级>" + + "用法:/giveall skinpart -g<類型> -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 个 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index e025fb6..a1ea46e 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -35,6 +35,7 @@ public class ServerTextCHT /// public class WordTextCHT { + public string SkinPart => "外觀部件"; public string Profile => "個人資料"; public string WeaponSkin => "武器外觀"; public string SupportCard => "支援卡"; @@ -242,7 +243,8 @@ public class GiveAllTextCHT public string Usage => "用法:/giveall weapon -p<特定> -l<等級>\n" + "用法:/giveall weaponskin -p<特定>\n" + "用法:/giveall card -p<特定> -l<等級>" + - "用法:/giveall profile -g<類型> -p<特定> -l<等級>"; + "用法:/giveall profile -g<類型> -p<特定> -l<等級>" + + "用法:/giveall skinpart -g<類型> -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 個 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index a0121b6..2eb8279 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -35,6 +35,7 @@ public class ServerTextEN /// public class WordTextEN { + public string SkinPart => "Skin Part"; public string Profile => "Profile"; public string WeaponSkin => "Weapon Skin"; public string Valk => "Valkyrie"; @@ -208,7 +209,8 @@ public class GiveAllTextEN public string Usage => "Usage: /giveall weapon -p -l\n" + "Usage: /giveall weaponskin -p\n" + "Usage: /giveall card -p -l" + - "Usage: /giveall profile -g -p -l"; + "Usage: /giveall profile -g -p -l" + + "Usage: /giveall skinpart -g -p -l"; public string NotFound => "{0} not found!"; public string GiveAllItems => "Added {0} {1} to player!"; } diff --git a/GameServer/Command/Commands/CommandGiveAll.cs b/GameServer/Command/Commands/CommandGiveAll.cs index 65977d3..b2cd3a0 100644 --- a/GameServer/Command/Commands/CommandGiveAll.cs +++ b/GameServer/Command/Commands/CommandGiveAll.cs @@ -150,4 +150,40 @@ public class CommandGiveAll : ICommands await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", I18NManager.Translate("Word.Profile"), profileItems.Count.ToString())); } + + [CommandMethod("skinpart")] + public async ValueTask GiveAllSkinPart(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (await arg.GetOption('p') is not int particular) return; + if (await arg.GetOption('l') is not int level) return; + if (await arg.GetOption('g') is not int genre) return; + + var detail = arg.GetInt(0); + var player = arg.Target!.Player!; + List skinPartItems = []; + if (detail == -1) + { + // add all + foreach (var config in GameData.CardSkinPartsData.Values) + { + var skinPart = await player.InventoryManager! + .AddSkinPartItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, config.Level, false); + if (skinPart != null) skinPartItems.Add(skinPart); + } + } + else + { + var skinPart = await player.InventoryManager!.AddSkinPartItem((ItemTypeEnum)genre, (uint)detail, (uint)particular, (uint)level, false); + if (skinPart == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.SkinPart"))); + return; + } + skinPartItems.Add(skinPart); + } + if (skinPartItems.Count > 0) await player.SendPacket(new PacketNtfCallScript(skinPartItems)); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", + I18NManager.Translate("Word.SkinPart"), skinPartItems.Count.ToString())); + } } \ No newline at end of file diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index f83d094..19f826f 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -270,4 +270,25 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) return profileInfo; } + + public async ValueTask AddSkinPartItem(ItemTypeEnum genre, uint detail, uint particular, uint level = 1, bool sendPacket = true) + { + if (genre != ItemTypeEnum.TYPE_CARD_SKIN_PART) return null; + var skinPartData = GameData.CardSkinPartsData.Values.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level); + if (skinPartData == null) return null; + var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level); + if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null; + var skinPartInfo = new BaseGameItemInfo + { + TemplateId = templateId, + UniqueId = InventoryData.NextUniqueUid++, + ItemType = genre, + ItemCount = 1 + }; + InventoryData.Items[skinPartInfo.UniqueId] = skinPartInfo; + + if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([skinPartInfo])); + + return skinPartInfo; + } } \ No newline at end of file diff --git a/GameServer/Server/CallGS/Handlers/Girl/GirlSkinParts_Update.cs b/GameServer/Server/CallGS/Handlers/Girl/GirlSkinParts_Update.cs new file mode 100644 index 0000000..f4e7f43 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/Girl/GirlSkinParts_Update.cs @@ -0,0 +1,57 @@ +using Azure; +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; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.Girl; + +[CallGSApi("GirlSkinParts_Update")] +public class GirlSkinParts_Update : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + if (req == null) + { + await CallGSRouter.SendScript(connection, "GirlSkinParts_Update", "{\"sErr\":\"error.BadParam\"}"); + return; + } + var player = connection.Player!; + var data = new List(); + foreach(var partId in req.PartsId) + { + var partData = player.InventoryManager.GetNormalItem(partId); + if (partData == null) continue; + + var partExcel = GameData.CardSkinPartsData.Values.FirstOrDefault(x => x.TemplateId == partData.TemplateId); + if (partExcel == null) continue; + + var skinData = player.InventoryManager.GetSkinItem(req.SkinId); + if (skinData == null) continue; + + skinData.PartSlots[partExcel.Detail] = partData.UniqueId; + data.Add(skinData); + } + + var sync = new NtfSyncPlayer + { + Items = { data.Select(x => x.ToProto()) } + }; + await CallGSRouter.SendScript(connection, "GirlSkinParts_Update", "{}", sync); + } +} + +internal sealed class GirlSkinPartsUpdateParam +{ + [JsonPropertyName("tbPartsID")] + public List PartsId { get; set; } = []; + + [JsonPropertyName("nSkinId")] + public uint SkinId { get; set; } +} From 2bf7554c533d33c3cd7113003381bba9723fad4f Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Wed, 29 Apr 2026 16:57:11 +0900 Subject: [PATCH 21/22] Unlock GM menu --- Common/Configuration/ConfigContainer.cs | 1 + .../Server/Packet/Recv/Login/HandlerReqLogin.cs | 11 +++++++++++ version.txt | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Common/Configuration/ConfigContainer.cs b/Common/Configuration/ConfigContainer.cs index 4b4dc03..4be7b60 100644 --- a/Common/Configuration/ConfigContainer.cs +++ b/Common/Configuration/ConfigContainer.cs @@ -60,6 +60,7 @@ public class ServerOption public string FallbackLanguage { get; set; } = "EN"; public string[] DefaultPermissions { get; set; } = ["Admin"]; public ServerProfile ServerProfile { get; set; } = new(); + public bool EnableGmMenu { get; set; } = true; public bool AutoCreateUser { get; set; } = true; public bool SavePersonalDebugFile { get; set; } = false; public bool AutoSendResponseWhenNoHandler { get; set; } = true; diff --git a/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs b/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs index dcf12c6..a46bf09 100644 --- a/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs +++ b/GameServer/Server/Packet/Recv/Login/HandlerReqLogin.cs @@ -53,6 +53,7 @@ public class HandlerReqLogin : Handler await connection.Player.OnEnterGame(); connection.Player.Connection = connection; await connection.SendPacket(new PacketRspLogin(connection.Player!)); + await SendDebugLoginState(connection); await connection.Player.OnHeartBeat(); await connection.SendPacket(new PacketNtfUpdateFriend(connection.Player!)); @@ -118,4 +119,14 @@ public class HandlerReqLogin : Handler }); } } + + private static async Task SendDebugLoginState(Connection connection) + { + var response = new JsonObject + { + ["IsDebug"] = ConfigManager.Config.ServerOption.EnableGmMenu + }; + + await CallGSRouter.SendScript(connection, "gm.notifylogin", response.ToJsonString()); + } } diff --git a/version.txt b/version.txt index 45e88f8..e79e7ee 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v=1.4 \ No newline at end of file +v=1.5 \ No newline at end of file From fcf0bf08432f2c247c83df91da4d08290988136e Mon Sep 17 00:00:00 2001 From: Naruse <71993948+DevilProMT@users.noreply.github.com> Date: Wed, 29 Apr 2026 16:00:10 +0800 Subject: [PATCH 22/22] add call item --- Common/Data/Excel/CallItemExcel.cs | 24 +++++++++++++ Common/Data/GameData.cs | 1 + .../Message/LanguageCHS.cs | 4 ++- .../Message/LanguageCHT.cs | 4 ++- .../Message/LanguageEN.cs | 4 ++- GameServer/Command/Commands/CommandGiveAll.cs | 36 +++++++++++++++++++ GameServer/Game/Inventory/InventoryManager.cs | 21 +++++++++++ 7 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 Common/Data/Excel/CallItemExcel.cs diff --git a/Common/Data/Excel/CallItemExcel.cs b/Common/Data/Excel/CallItemExcel.cs new file mode 100644 index 0000000..2c1fb60 --- /dev/null +++ b/Common/Data/Excel/CallItemExcel.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("call_item.json")] +public class CallItemExcel : ExcelResource +{ + public uint Genre { get; set; } + public uint Detail { get; set; } + public uint Particular { get; set; } + public uint Level { get; set; } + public string I18n { get; set; } = ""; + + public override uint GetId() + { + return (uint)I18n.GetHashCode(); + } + + public override void Loaded() + { + GameData.CallItemData.Add(GetId(), this); + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index b1d9454..1a90c0c 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -24,6 +24,7 @@ public static class GameData public static Dictionary DailyLevelData { get; private set; } = []; public static Dictionary ProfileData { get; private set; } = []; public static Dictionary CardSkinPartsData { get; private set; } = []; + public static Dictionary CallItemData { get; private set; } = []; } public static class GameResourceTemplateId diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index 67821b0..f9f5ac4 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -35,6 +35,7 @@ public class ServerTextCHS /// public class WordTextCHS { + public string CallItem => "召唤道具"; public string SkinPart => "皮肤部件"; public string Profile => "个人资料"; public string WeaponSkin => "武器皮肤"; @@ -244,7 +245,8 @@ public class GiveAllTextCHS "用法:/giveall weaponskin -p<特定>\n" + "用法:/giveall card -p<特定> -l<等級>" + "用法:/giveall profile -g<类型> -p<特定> -l<等级>" + - "用法:/giveall skinpart -g<類型> -p<特定> -l<等級>"; + "用法:/giveall skinpart -g<類型> -p<特定> -l<等級>" + + "用法:/giveall call -g<類型> -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 个 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageCHT.cs b/Common/Internationalization/Message/LanguageCHT.cs index a1ea46e..8ad0653 100644 --- a/Common/Internationalization/Message/LanguageCHT.cs +++ b/Common/Internationalization/Message/LanguageCHT.cs @@ -35,6 +35,7 @@ public class ServerTextCHT /// public class WordTextCHT { + public string CallItem => "召喚道具"; public string SkinPart => "外觀部件"; public string Profile => "個人資料"; public string WeaponSkin => "武器外觀"; @@ -244,7 +245,8 @@ public class GiveAllTextCHT "用法:/giveall weaponskin -p<特定>\n" + "用法:/giveall card -p<特定> -l<等級>" + "用法:/giveall profile -g<類型> -p<特定> -l<等級>" + - "用法:/giveall skinpart -g<類型> -p<特定> -l<等級>"; + "用法:/giveall skinpart -g<類型> -p<特定> -l<等級>" + + "用法:/giveall call -g<類型> -p<特定> -l<等級>"; public string NotFound => "未找到 {0}!"; public string GiveAllItems => "已向玩家添加 {0} 個 {1}!"; } diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index 2eb8279..03d4f66 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -35,6 +35,7 @@ public class ServerTextEN /// public class WordTextEN { + public string CallItem => "Call Item"; public string SkinPart => "Skin Part"; public string Profile => "Profile"; public string WeaponSkin => "Weapon Skin"; @@ -210,7 +211,8 @@ public class GiveAllTextEN "Usage: /giveall weaponskin -p\n" + "Usage: /giveall card -p -l" + "Usage: /giveall profile -g -p -l" + - "Usage: /giveall skinpart -g -p -l"; + "Usage: /giveall skinpart -g -p -l" + + "Usage: /giveall call -g -p -l"; public string NotFound => "{0} not found!"; public string GiveAllItems => "Added {0} {1} to player!"; } diff --git a/GameServer/Command/Commands/CommandGiveAll.cs b/GameServer/Command/Commands/CommandGiveAll.cs index b2cd3a0..161fae9 100644 --- a/GameServer/Command/Commands/CommandGiveAll.cs +++ b/GameServer/Command/Commands/CommandGiveAll.cs @@ -186,4 +186,40 @@ public class CommandGiveAll : ICommands await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", I18NManager.Translate("Word.SkinPart"), skinPartItems.Count.ToString())); } + + [CommandMethod("call")] + public async ValueTask GiveAllCallItem(CommandArg arg) + { + if (!await arg.CheckOnlineTarget()) return; + if (await arg.GetOption('p') is not int particular) return; + if (await arg.GetOption('l') is not int level) return; + if (await arg.GetOption('g') is not int genre) return; + + var detail = arg.GetInt(0); + var player = arg.Target!.Player!; + List callItems = []; + if (detail == -1) + { + // add all + foreach (var config in GameData.CallItemData.Values) + { + var callItem = await player.InventoryManager! + .AddCallItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, config.Level, false); + if (callItem != null) callItems.Add(callItem); + } + } + else + { + var callItem = await player.InventoryManager!.AddCallItem((ItemTypeEnum)genre, (uint)detail, (uint)particular, (uint)level, false); + if (callItem == null) + { + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.CallItem"))); + return; + } + callItems.Add(callItem); + } + if (callItems.Count > 0) await player.SendPacket(new PacketNtfCallScript(callItems)); + await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", + I18NManager.Translate("Word.CallItem"), callItems.Count.ToString())); + } } \ No newline at end of file diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index 19f826f..27a8915 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -291,4 +291,25 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) return skinPartInfo; } + + public async ValueTask AddCallItem(ItemTypeEnum genre, uint detail, uint particular, uint level = 1, bool sendPacket = true) + { + if (genre != ItemTypeEnum.TYPE_CALL) return null; + var callData = GameData.CallItemData.Values.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level); + if (callData == null) return null; + var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level); + if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null; + var callInfo = new BaseGameItemInfo + { + TemplateId = templateId, + UniqueId = InventoryData.NextUniqueUid++, + ItemType = genre, + ItemCount = 1 + }; + InventoryData.Items[callInfo.UniqueId] = callInfo; + + if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([callInfo])); + + return callInfo; + } } \ No newline at end of file