From 0058ba0db6b2bc3eb54c0ac382ae15b52df10228 Mon Sep 17 00:00:00 2001 From: Kei-Luna Date: Fri, 15 May 2026 16:07:36 +0900 Subject: [PATCH] Support Card Affix --- Common/Data/Excel/SupportAffixExcel.cs | 27 +++++++++++++ Common/Data/Excel/SupportAffixPoolExcel.cs | 35 ++++++++++++++++ Common/Data/Excel/SupportCardExcel.cs | 8 ++++ Common/Data/GameData.cs | 2 + Common/Database/Inventory/InventoryData.cs | 3 ++ GameServer/Game/Inventory/InventoryManager.cs | 11 +++++ .../Game/Support/SupportAffixService.cs | 40 +++++++++++++++++++ .../SupporterCard/SupporterCard_Upgrade.cs | 18 +++++++++ 8 files changed, 144 insertions(+) create mode 100644 Common/Data/Excel/SupportAffixExcel.cs create mode 100644 Common/Data/Excel/SupportAffixPoolExcel.cs create mode 100644 GameServer/Game/Support/SupportAffixService.cs diff --git a/Common/Data/Excel/SupportAffixExcel.cs b/Common/Data/Excel/SupportAffixExcel.cs new file mode 100644 index 0000000..5c4487f --- /dev/null +++ b/Common/Data/Excel/SupportAffixExcel.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("item/support/affix.json")] +public class SupportAffixExcel : ExcelResource +{ + [JsonProperty("ID")] public int Id { get; set; } + [JsonExtensionData] public IDictionary ExtraData { get; set; } = new Dictionary(); + + public int TierCount => + ExtraData + .Where(x => x.Key != "ID" && x.Key != "Sift" && x.Key != "Comment") + .Select(x => x.Value) + .OfType() + .Select(x => x.Count) + .DefaultIfEmpty(0) + .Max(); + + public override uint GetId() => (uint)Id; + + public override void Loaded() + { + GameData.SupportAffixData[Id] = this; + } +} diff --git a/Common/Data/Excel/SupportAffixPoolExcel.cs b/Common/Data/Excel/SupportAffixPoolExcel.cs new file mode 100644 index 0000000..3cc6bdd --- /dev/null +++ b/Common/Data/Excel/SupportAffixPoolExcel.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("item/support/affix_pool.json")] +public class SupportAffixPoolExcel : ExcelResource +{ + [JsonProperty("ID")] public int Id { get; set; } + public List AffixGroup1 { get; set; } = []; + public int Weight1 { get; set; } + public List AffixGroup2 { get; set; } = []; + public int Weight2 { get; set; } + public List AffixGroup3 { get; set; } = []; + public int Weight3 { get; set; } + public List AffixGroup4 { get; set; } = []; + public int Weight4 { get; set; } + + public IEnumerable<(IReadOnlyList Affixs, int Weight)> Groups + { + get + { + if (AffixGroup1.Count > 0 && Weight1 > 0) yield return (AffixGroup1, Weight1); + if (AffixGroup2.Count > 0 && Weight2 > 0) yield return (AffixGroup2, Weight2); + if (AffixGroup3.Count > 0 && Weight3 > 0) yield return (AffixGroup3, Weight3); + if (AffixGroup4.Count > 0 && Weight4 > 0) yield return (AffixGroup4, Weight4); + } + } + + public override uint GetId() => (uint)Id; + + public override void Loaded() + { + GameData.SupportAffixPoolData[Id] = this; + } +} diff --git a/Common/Data/Excel/SupportCardExcel.cs b/Common/Data/Excel/SupportCardExcel.cs index 8502909..8231ee1 100644 --- a/Common/Data/Excel/SupportCardExcel.cs +++ b/Common/Data/Excel/SupportCardExcel.cs @@ -11,7 +11,9 @@ public class SupportCardExcel : ExcelResource public uint Level { get; set; } public uint Icon { get; set; } public uint ProvideExp { get; set; } + public uint Color { get; set; } [JsonProperty("LevelLimitID")] public int LevelLimitId { get; set; } + [JsonProperty("AffixPool")] public List AffixPool { get; set; } = []; public uint MaxLevel => LevelLimitId switch { @@ -21,6 +23,12 @@ public class SupportCardExcel : ExcelResource _ => 10 }; + // Number of affixes granted initially + public int InitialAffixCount => Color >= 5 ? 2 : 1; + + // Total maximum affixes (including ones unlocked at max level) + public int TotalAffixCount => Color >= 5 ? 3 : 2; + public ulong TemplateId => GameResourceTemplateId.FromGdpl(Genre, Detail, Particular, Level); public override uint GetId() => Icon; diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 5f81860..6a87f96 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -23,6 +23,8 @@ 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 SupportAffixData { get; private set; } = []; + public static Dictionary SupportAffixPoolData { get; private set; } = []; public static Dictionary WeaponSkinData { get; private set; } = []; public static Dictionary DailyLevelData { get; private set; } = []; public static Dictionary ProfileData { get; private set; } = []; diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index 4c3125c..575b5f9 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -106,6 +106,8 @@ public class GameSkinInfo : BaseGameItemInfo public class GameSupportCardInfo : BaseGameItemInfo { public uint AffixId { get; set; } + [SugarColumn(IsJson = true)] public List Affixs { get; set; } = []; + public override Item ToProto() { var proto = new Item @@ -120,6 +122,7 @@ public class GameSupportCardInfo : BaseGameItemInfo Exp = Exp } }; + proto.Enhance.Affixs.AddRange(Affixs); proto.Slots[(uint)ItemSupportCardSlotTypeEnum.SLOT_AFFIXINDEX] = AffixId; return proto; } diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index c3d79fd..1c0bbd6 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -4,6 +4,7 @@ using MikuSB.Database; using MikuSB.Database.Inventory; using MikuSB.Enums.Item; using MikuSB.GameServer.Game.Player; +using MikuSB.GameServer.Game.Support; using MikuSB.GameServer.Server.Packet.Send.Misc; namespace MikuSB.GameServer.Game.Inventory; @@ -135,7 +136,17 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) ItemType = genre, ItemCount = 1, Level = cardLevel, + AffixId = 1, }; + + var initialCount = spCard.InitialAffixCount; + for (int i = 0; i < initialCount && i < spCard.AffixPool.Count; i++) + { + var (affixId, tier) = SupportAffixService.GenerateRandomAffix(spCard.AffixPool[i]); + info.Affixs.Add(affixId); + info.Affixs.Add(tier); + } + InventoryData.SupportCards[info.UniqueId] = info; if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([info])); diff --git a/GameServer/Game/Support/SupportAffixService.cs b/GameServer/Game/Support/SupportAffixService.cs new file mode 100644 index 0000000..dc28b67 --- /dev/null +++ b/GameServer/Game/Support/SupportAffixService.cs @@ -0,0 +1,40 @@ +using MikuSB.Data; + +namespace MikuSB.GameServer.Game.Support; + +public static class SupportAffixService +{ + // Returns (affixId, tier) - both 1-based. Returns (0,0) if pool not found. + public static (uint AffixId, uint Tier) GenerateRandomAffix(int poolId) + { + if (!GameData.SupportAffixPoolData.TryGetValue(poolId, out var pool)) + return (0, 0); + + var groups = pool.Groups.ToList(); + if (groups.Count == 0) + return (0, 0); + + var totalWeight = groups.Sum(x => x.Weight); + var roll = Random.Shared.Next(totalWeight); + var cumulative = 0; + var selectedAffixs = groups[0].Affixs; + + foreach (var (affixIds, weight) in groups) + { + cumulative += weight; + if (roll < cumulative) + { + selectedAffixs = affixIds; + break; + } + } + + if (selectedAffixs.Count == 0) + return (0, 0); + + var affixId = selectedAffixs[Random.Shared.Next(selectedAffixs.Count)]; + var tierCount = GameData.SupportAffixData.GetValueOrDefault(affixId)?.TierCount ?? 5; + var tier = (uint)(Random.Shared.Next(tierCount) + 1); + return ((uint)affixId, tier); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs index 2b84135..4b08f99 100644 --- a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs @@ -1,5 +1,6 @@ using MikuSB.Data; using MikuSB.Database; +using MikuSB.GameServer.Game.Support; using MikuSB.Proto; using System.Text.Json; using System.Text.Json.Serialization; @@ -81,6 +82,23 @@ public class SupporterCard_Upgrade : ICallGSHandler { supportCard.Exp = 0; supportCard.Level = maxLevel; + + // Unlock next affix slot when reaching max level for the first time + if (supportCardExcel != null) + { + var currentSlots = supportCard.Affixs.Count / 2; + var totalSlots = supportCardExcel.TotalAffixCount; + if (currentSlots < totalSlots && currentSlots < supportCardExcel.AffixPool.Count) + { + var poolId = supportCardExcel.AffixPool[currentSlots]; + var (affixId, tier) = SupportAffixService.GenerateRandomAffix(poolId); + if (affixId > 0) + { + supportCard.Affixs.Add(affixId); + supportCard.Affixs.Add(tier); + } + } + } } syncItems.Add(supportCard.ToProto());