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] 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; } +}