diff --git a/Common/Data/Excel/SupportCardExcel.cs b/Common/Data/Excel/SupportCardExcel.cs index 8231ee1..91daf3a 100644 --- a/Common/Data/Excel/SupportCardExcel.cs +++ b/Common/Data/Excel/SupportCardExcel.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace MikuSB.Data.Excel; @@ -14,6 +15,9 @@ public class SupportCardExcel : ExcelResource public uint Color { get; set; } [JsonProperty("LevelLimitID")] public int LevelLimitId { get; set; } [JsonProperty("AffixPool")] public List AffixPool { get; set; } = []; + [JsonProperty("AffixCost")] public JToken? AffixCostRaw { get; set; } + [JsonProperty("InitialAffixCost")] public JToken? InitialAffixCostRaw { get; set; } + [JsonProperty("FixedAffixCost")] public JToken? FixedAffixCostRaw { get; set; } public uint MaxLevel => LevelLimitId switch { @@ -23,12 +27,19 @@ 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; + [JsonIgnore] + public IReadOnlyList AffixCost => ParseFlatCost(AffixCostRaw); + + [JsonIgnore] + public IReadOnlyList> InitialAffixCost => ParseNestedCost(InitialAffixCostRaw); + + [JsonIgnore] + public IReadOnlyList FixedAffixCost => ParseFlatCost(FixedAffixCostRaw); + public ulong TemplateId => GameResourceTemplateId.FromGdpl(Genre, Detail, Particular, Level); public override uint GetId() => Icon; @@ -37,4 +48,23 @@ public class SupportCardExcel : ExcelResource { GameData.SupportCardData.Add(this); } + + private static IReadOnlyList ParseFlatCost(JToken? token) + { + if (token is not JArray array) + return []; + + return array.Select(x => x.Value()).ToArray(); + } + + private static IReadOnlyList> ParseNestedCost(JToken? token) + { + if (token is not JArray outer) + return []; + + var result = new List>(); + foreach (var entry in outer.OfType()) + result.Add(entry.Select(x => x.Value()).ToArray()); + return result; + } } diff --git a/Common/Data/Excel/SupportFixedExcel.cs b/Common/Data/Excel/SupportFixedExcel.cs new file mode 100644 index 0000000..64aa182 --- /dev/null +++ b/Common/Data/Excel/SupportFixedExcel.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace MikuSB.Data.Excel; + +[ResourceEntity("item/support/fixed.json")] +public class SupportFixedExcel : ExcelResource +{ + [JsonProperty("ID")] public int Id { get; set; } + public int Num { get; set; } + public List Item { get; set; } = []; + + public override uint GetId() => (uint)Id; + + public override void Loaded() + { + GameData.SupportFixedData[Id] = this; + } +} diff --git a/Common/Data/GameData.cs b/Common/Data/GameData.cs index 6a87f96..5a665c8 100644 --- a/Common/Data/GameData.cs +++ b/Common/Data/GameData.cs @@ -25,6 +25,7 @@ public static class GameData public static List SupportCardData { get; private set; } = []; public static Dictionary SupportAffixData { get; private set; } = []; public static Dictionary SupportAffixPoolData { get; private set; } = []; + public static Dictionary SupportFixedData { 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 575b5f9..9398034 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -1,4 +1,4 @@ -using MikuSB.Enums.Item; +using MikuSB.Enums.Item; using MikuSB.Proto; using SqlSugar; @@ -10,19 +10,19 @@ public class InventoryData : BaseDatabaseDataHelper public uint NextUniqueUid { get; set; } = 100000; [SugarColumn(IsJson = true)] - public Dictionary Items { get; set; } = []; // Key: UniqueId + public Dictionary Items { get; set; } = []; [SugarColumn(IsJson = true)] - public Dictionary Weapons { get; set; } = []; // Key: UniqueId + public Dictionary Weapons { get; set; } = []; [SugarColumn(IsJson = true)] - public Dictionary Skins { get; set; } = []; // Key: UniqueId + public Dictionary Skins { get; set; } = []; [SugarColumn(IsJson = true)] - public Dictionary SupportCards { get; set; } = []; // Key: UniqueId + public Dictionary SupportCards { get; set; } = []; [SugarColumn(IsJson = true)] - public Dictionary SkinTypesBySkinId { get; set; } = []; // Key: nSkinId, Value: client nType + public Dictionary SkinTypesBySkinId { get; set; } = []; } public class BaseGameItemInfo @@ -63,6 +63,7 @@ public abstract class GrowableItemInfo : BaseGameItemInfo public class GameWeaponInfo : GrowableItemInfo { [SugarColumn(IsJson = true)] public Dictionary PartSlots { get; set; } = []; + public override Item ToProto() { var proto = new Item @@ -79,14 +80,17 @@ public class GameWeaponInfo : GrowableItemInfo Evolue = Evolue } }; - foreach (var (slot, uid) in PartSlots) proto.Slots[slot] = uid; + foreach (var (slot, uid) in PartSlots) + proto.Slots[slot] = uid; return proto; } } + public class GameSkinInfo : BaseGameItemInfo { [SugarColumn(IsJson = true)] public Dictionary PartSlots { get; set; } = []; public uint SkinType { get; set; } + public override Item ToProto() { var proto = new Item @@ -97,12 +101,12 @@ 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; + foreach (var (slot, uid) in PartSlots) + proto.Slots[slot] = uid; return proto; } } - public class GameSupportCardInfo : BaseGameItemInfo { public uint AffixId { get; set; } diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index 8a45455..5141eee 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -136,7 +136,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) ItemType = genre, ItemCount = 1, Level = cardLevel, - AffixId = 1, + AffixId = 0, }; var affixCount = cardLevel >= spCard.MaxLevel ? spCard.TotalAffixCount : spCard.InitialAffixCount; @@ -144,8 +144,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) { var (affixId, tier) = SupportAffixService.GenerateRandomAffix(spCard.AffixPool[i]); if (affixId == 0) continue; - info.Affixs.Add(affixId); - info.Affixs.Add(tier); + SupportAffixStateService.SetAffix(info, i + 1, affixId, tier); } InventoryData.SupportCards[info.UniqueId] = info; diff --git a/GameServer/Game/Support/SupportAffixService.cs b/GameServer/Game/Support/SupportAffixService.cs index dc28b67..7420d89 100644 --- a/GameServer/Game/Support/SupportAffixService.cs +++ b/GameServer/Game/Support/SupportAffixService.cs @@ -4,8 +4,7 @@ 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) + public static (uint AffixId, uint Tier) GenerateRandomAffix(int poolId, IEnumerable? excludedAffixIds = null) { if (!GameData.SupportAffixPoolData.TryGetValue(poolId, out var pool)) return (0, 0); @@ -32,9 +31,20 @@ public static class SupportAffixService if (selectedAffixs.Count == 0) return (0, 0); - var affixId = selectedAffixs[Random.Shared.Next(selectedAffixs.Count)]; + var excluded = excludedAffixIds?.ToHashSet() ?? []; + var candidates = selectedAffixs.Where(x => !excluded.Contains((uint)x)).ToList(); + if (candidates.Count == 0) + candidates = selectedAffixs.ToList(); + + var affixId = candidates[Random.Shared.Next(candidates.Count)]; var tierCount = GameData.SupportAffixData.GetValueOrDefault(affixId)?.TierCount ?? 5; var tier = (uint)(Random.Shared.Next(tierCount) + 1); return ((uint)affixId, tier); } + + public static uint GenerateTier(uint affixId) + { + var tierCount = GameData.SupportAffixData.GetValueOrDefault((int)affixId)?.TierCount ?? 5; + return (uint)(Random.Shared.Next(tierCount) + 1); + } } diff --git a/GameServer/Game/Support/SupportAffixStateService.cs b/GameServer/Game/Support/SupportAffixStateService.cs new file mode 100644 index 0000000..c7dc32d --- /dev/null +++ b/GameServer/Game/Support/SupportAffixStateService.cs @@ -0,0 +1,70 @@ +using MikuSB.Database.Inventory; + +namespace MikuSB.GameServer.Game.Support; + +public static class SupportAffixStateService +{ + public const int PairSize = 2; + public const int MaxLogicalSlots = 5; + public const int ActiveThirdAffixSlot = 3; + public const int PendingMaxAffixSlot = 4; + public const int PendingInitialAffixSlot = 5; + + public static void EnsureCapacity(GameSupportCardInfo card, int logicalSlot = MaxLogicalSlots) + { + var minCount = Math.Clamp(logicalSlot, 1, MaxLogicalSlots) * PairSize; + while (card.Affixs.Count < minCount) + card.Affixs.Add(0); + } + + public static (uint AffixId, uint Tier) GetAffix(GameSupportCardInfo card, int logicalSlot) + { + if (logicalSlot < 1 || logicalSlot > MaxLogicalSlots) + return (0, 0); + + var index = (logicalSlot - 1) * PairSize; + if (card.Affixs.Count <= index + 1) + return (0, 0); + + return (card.Affixs[index], card.Affixs[index + 1]); + } + + public static bool HasAffix(GameSupportCardInfo card, int logicalSlot) + { + var (affixId, tier) = GetAffix(card, logicalSlot); + return affixId > 0 && tier > 0; + } + + public static void SetAffix(GameSupportCardInfo card, int logicalSlot, uint affixId, uint tier) + { + if (logicalSlot < 1 || logicalSlot > MaxLogicalSlots) + return; + + EnsureCapacity(card, logicalSlot); + var index = (logicalSlot - 1) * PairSize; + card.Affixs[index] = affixId; + card.Affixs[index + 1] = tier; + } + + public static void ClearAffix(GameSupportCardInfo card, int logicalSlot) + { + SetAffix(card, logicalSlot, 0, 0); + } + + public static void CopyAffix(GameSupportCardInfo card, int fromSlot, int toSlot) + { + var (affixId, tier) = GetAffix(card, fromSlot); + SetAffix(card, toSlot, affixId, tier); + } + + public static uint GetVisibleInitialAffixIndex(GameSupportCardInfo card) + { + return HasAffix(card, PendingInitialAffixSlot) ? card.AffixId : 0; + } + + public static void NormalizePendingState(GameSupportCardInfo card) + { + if (!HasAffix(card, PendingInitialAffixSlot)) + card.AffixId = 0; + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCardAffixShared.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCardAffixShared.cs new file mode 100644 index 0000000..78a8e4c --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCardAffixShared.cs @@ -0,0 +1,153 @@ +using MikuSB.Data; +using MikuSB.Data.Excel; +using MikuSB.Database; +using MikuSB.Database.Inventory; +using MikuSB.Database.Player; +using MikuSB.GameServer.Game.Support; +using MikuSB.Proto; +using System.Text.Json.Serialization; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +internal static class SupporterCardAffixShared +{ + public const uint BaseGid = 150; + public const uint FixedResetSid = 1; + + public static SupportCardExcel? GetExcel(GameSupportCardInfo card) + { + return GameData.SupportCardData.FirstOrDefault(x => x.TemplateId == card.TemplateId); + } + + public static async Task SendResetResponse(Connection connection, NtfSyncPlayer? sync = null) + { + await CallGSRouter.SendScript(connection, "SupporterCard_ResetAffix", "null", sync!); + } + + public static async Task SendSelectResponse(Connection connection, NtfSyncPlayer? sync = null) + { + await CallGSRouter.SendScript(connection, "SupporterCard_SelectAffix", "null", sync!); + } + + public static List ConsumeCostItems(Connection connection, IEnumerable> costs) + { + var player = connection.Player!; + var syncItems = new List(); + + foreach (var cost in costs) + { + if (cost.Count < 5) + continue; + + var templateId = GameResourceTemplateId.FromGdpl(cost); + var item = player.InventoryManager.InventoryData.Items.Values.FirstOrDefault(x => x.TemplateId == templateId); + if (item == null || item.ItemCount < cost[4]) + throw new InvalidOperationException("support affix material not enough"); + + item.ItemCount -= cost[4]; + var proto = item.ToProto(); + if (item.ItemCount == 0) + { + player.InventoryManager.InventoryData.Items.Remove(item.UniqueId); + proto.Count = 0; + } + syncItems.Add(proto); + } + + return syncItems; + } + + public static bool HasEnoughItems(Connection connection, IEnumerable> costs) + { + var items = connection.Player!.InventoryManager.InventoryData.Items.Values; + return costs.All(cost => + { + if (cost.Count < 5) + return false; + + var templateId = GameResourceTemplateId.FromGdpl(cost); + var item = items.FirstOrDefault(x => x.TemplateId == templateId); + return item != null && item.ItemCount >= cost[4]; + }); + } + + public static PlayerAttr GetOrCreateAttr(PlayerGameData data, uint gid, uint sid) + { + var attr = data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid); + if (attr != null) + return attr; + + attr = new PlayerAttr { Gid = gid, Sid = sid, Val = 0 }; + data.Attrs.Add(attr); + return attr; + } + + public static void SetAttr(Connection connection, NtfSyncPlayer sync, uint gid, uint sid, uint value) + { + var player = connection.Player!; + var attr = GetOrCreateAttr(player.Data, gid, sid); + attr.Val = value; + sync.Custom[player.ToPackedAttrKey(gid, sid)] = value; + sync.Custom[player.ToShiftedAttrKey(gid, sid)] = value; + } + + public static IEnumerable GetActiveAffixIds(GameSupportCardInfo card, params int[] ignoreSlots) + { + var ignored = ignoreSlots.ToHashSet(); + for (var slot = 1; slot <= SupportAffixStateService.ActiveThirdAffixSlot; slot++) + { + if (ignored.Contains(slot)) + continue; + + var (affixId, _) = SupportAffixStateService.GetAffix(card, slot); + if (affixId > 0) + yield return affixId; + } + } + + public static void Save(Connection connection) + { + var player = connection.Player!; + DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData); + DatabaseHelper.SaveDatabaseType(player.Data); + } +} + +internal sealed class SupporterCardIdParam +{ + [JsonPropertyName("Id")] + public int SupportCardUid { get; set; } +} + +internal sealed class SupporterCardSelectParam +{ + [JsonPropertyName("Id")] + public int SupportCardUid { get; set; } + + [JsonPropertyName("SelectNew")] + public bool SelectNew { get; set; } +} + +internal sealed class SupporterCardResetInitialParam +{ + [JsonPropertyName("Id")] + public int SupportCardUid { get; set; } + + [JsonPropertyName("Index")] + public int Index { get; set; } + + [JsonPropertyName("FixedId")] + public uint FixedId { get; set; } +} + +internal sealed class SupporterCardSelectInitialParam +{ + [JsonPropertyName("Id")] + public int SupportCardUid { get; set; } + + [JsonPropertyName("Index")] + public int Index { get; set; } + + [JsonPropertyName("SelectNew")] + public bool SelectNew { get; set; } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_FixedResetInitialAffix.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_FixedResetInitialAffix.cs new file mode 100644 index 0000000..54e31a0 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_FixedResetInitialAffix.cs @@ -0,0 +1,10 @@ +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +[CallGSApi("SupporterCard_FixedResetInitialAffix")] +public class SupporterCard_FixedResetInitialAffix : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + await SupporterCard_ResetInitialAffix.Reset(connection, param, fixedMode: true); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ReceiveFixedItem.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ReceiveFixedItem.cs new file mode 100644 index 0000000..9f114a0 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ReceiveFixedItem.cs @@ -0,0 +1,57 @@ +using MikuSB.Data; +using MikuSB.Proto; +using System.Text.Json.Nodes; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +[CallGSApi("SupporterCard_ReceiveFixedItem")] +public class SupporterCard_ReceiveFixedItem : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var player = connection.Player!; + if (!GameData.SupportFixedData.TryGetValue(1, out var fixedCfg) || fixedCfg.Item.Count < 5 || fixedCfg.Num <= 0) + { + await CallGSRouter.SendScript(connection, "SupporterCard_ReceiveFixedItem", "{}"); + return; + } + + var attr = SupporterCardAffixShared.GetOrCreateAttr(player.Data, SupporterCardAffixShared.BaseGid, SupporterCardAffixShared.FixedResetSid); + var claimCount = attr.Val / (uint)fixedCfg.Num; + if (claimCount == 0) + { + await CallGSRouter.SendScript(connection, "SupporterCard_ReceiveFixedItem", "{}"); + return; + } + + attr.Val %= (uint)fixedCfg.Num; + + var rewardTemplateId = (uint)GameResourceTemplateId.FromGdpl(fixedCfg.Item); + var rewardItem = GameData.SuppliesData.GetValueOrDefault(rewardTemplateId); + if (rewardItem == null) + { + await CallGSRouter.SendScript(connection, "SupporterCard_ReceiveFixedItem", "{}"); + return; + } + + var granted = await player.InventoryManager.AddSuppliesItem(rewardItem, claimCount * fixedCfg.Item[4], sendPacket: false); + + var sync = new NtfSyncPlayer(); + if (granted != null) + sync.Items.Add(granted.ToProto()); + SupporterCardAffixShared.SetAttr(connection, sync, SupporterCardAffixShared.BaseGid, SupporterCardAffixShared.FixedResetSid, attr.Val); + SupporterCardAffixShared.Save(connection); + + var arg = new JsonObject + { + ["tbRewards"] = new JsonArray( + (int)fixedCfg.Item[0], + (int)fixedCfg.Item[1], + (int)fixedCfg.Item[2], + (int)fixedCfg.Item[3], + (int)(claimCount * fixedCfg.Item[4])) + }.ToJsonString(); + + await CallGSRouter.SendScript(connection, "SupporterCard_ReceiveFixedItem", arg, sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ResetAffix.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ResetAffix.cs new file mode 100644 index 0000000..a01773e --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ResetAffix.cs @@ -0,0 +1,38 @@ +using MikuSB.GameServer.Game.Support; +using MikuSB.Proto; +using System.Text.Json; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +[CallGSApi("SupporterCard_ResetAffix")] +public class SupporterCard_ResetAffix : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + var card = req == null ? null : connection.Player!.InventoryManager.GetSupportCardItem((uint)req.SupportCardUid); + var excel = card == null ? null : SupporterCardAffixShared.GetExcel(card); + if (card == null || excel == null || excel.AffixCost.Count < 5 || !SupportAffixStateService.HasAffix(card, SupportAffixStateService.ActiveThirdAffixSlot)) + { + await SupporterCardAffixShared.SendResetResponse(connection); + return; + } + + var costs = new[] { excel.AffixCost }; + if (!SupporterCardAffixShared.HasEnoughItems(connection, costs)) + { + await SupporterCardAffixShared.SendResetResponse(connection); + return; + } + + var sync = new NtfSyncPlayer(); + sync.Items.AddRange(SupporterCardAffixShared.ConsumeCostItems(connection, costs)); + var excluded = SupporterCardAffixShared.GetActiveAffixIds(card, SupportAffixStateService.ActiveThirdAffixSlot); + var (affixId, tier) = SupportAffixService.GenerateRandomAffix(excel.AffixPool[SupportAffixStateService.ActiveThirdAffixSlot - 1], excluded); + SupportAffixStateService.SetAffix(card, SupportAffixStateService.PendingMaxAffixSlot, affixId, tier); + sync.Items.Add(card.ToProto()); + + SupporterCardAffixShared.Save(connection); + await SupporterCardAffixShared.SendResetResponse(connection, sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ResetInitialAffix.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ResetInitialAffix.cs new file mode 100644 index 0000000..5352c1c --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_ResetInitialAffix.cs @@ -0,0 +1,60 @@ +using MikuSB.GameServer.Game.Support; +using MikuSB.Proto; +using System.Text.Json; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +[CallGSApi("SupporterCard_ResetInitialAffix")] +public class SupporterCard_ResetInitialAffix : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + await Reset(connection, param, fixedMode: false); + } + + internal static async Task Reset(Connection connection, string param, bool fixedMode) + { + var req = JsonSerializer.Deserialize(param); + var card = req == null ? null : connection.Player!.InventoryManager.GetSupportCardItem((uint)req.SupportCardUid); + var excel = card == null ? null : SupporterCardAffixShared.GetExcel(card); + if (req == null || card == null || excel == null || req.Index is < 1 or > 2 || excel.AffixPool.Count < req.Index) + { + await SupporterCardAffixShared.SendResetResponse(connection); + return; + } + + var costs = fixedMode ? new[] { excel.FixedAffixCost } : excel.InitialAffixCost; + if (!costs.Any() || !SupporterCardAffixShared.HasEnoughItems(connection, costs)) + { + await SupporterCardAffixShared.SendResetResponse(connection); + return; + } + + var sync = new NtfSyncPlayer(); + sync.Items.AddRange(SupporterCardAffixShared.ConsumeCostItems(connection, costs)); + + uint affixId; + uint tier; + if (fixedMode && req.FixedId > 0) + { + affixId = req.FixedId; + tier = SupportAffixService.GenerateTier(affixId); + } + else + { + var excluded = SupporterCardAffixShared.GetActiveAffixIds(card, req.Index); + (affixId, tier) = SupportAffixService.GenerateRandomAffix(excel.AffixPool[req.Index - 1], excluded); + } + + SupportAffixStateService.SetAffix(card, SupportAffixStateService.PendingInitialAffixSlot, affixId, tier); + card.AffixId = (uint)req.Index; + + var attr = SupporterCardAffixShared.GetOrCreateAttr(connection.Player!.Data, SupporterCardAffixShared.BaseGid, SupporterCardAffixShared.FixedResetSid); + attr.Val += 1; + SupporterCardAffixShared.SetAttr(connection, sync, SupporterCardAffixShared.BaseGid, SupporterCardAffixShared.FixedResetSid, attr.Val); + + sync.Items.Add(card.ToProto()); + SupporterCardAffixShared.Save(connection); + await SupporterCardAffixShared.SendResetResponse(connection, sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_SelectAffix.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_SelectAffix.cs new file mode 100644 index 0000000..098a1e5 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_SelectAffix.cs @@ -0,0 +1,30 @@ +using MikuSB.GameServer.Game.Support; +using MikuSB.Proto; +using System.Text.Json; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +[CallGSApi("SupporterCard_SelectAffix")] +public class SupporterCard_SelectAffix : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + var card = req == null ? null : connection.Player!.InventoryManager.GetSupportCardItem((uint)req.SupportCardUid); + if (card == null || !SupportAffixStateService.HasAffix(card, SupportAffixStateService.PendingMaxAffixSlot)) + { + await SupporterCardAffixShared.SendSelectResponse(connection); + return; + } + + if (req!.SelectNew) + SupportAffixStateService.CopyAffix(card, SupportAffixStateService.PendingMaxAffixSlot, SupportAffixStateService.ActiveThirdAffixSlot); + + SupportAffixStateService.ClearAffix(card, SupportAffixStateService.PendingMaxAffixSlot); + + var sync = new NtfSyncPlayer(); + sync.Items.Add(card.ToProto()); + SupporterCardAffixShared.Save(connection); + await SupporterCardAffixShared.SendSelectResponse(connection, sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_SelectInitialAffix.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_SelectInitialAffix.cs new file mode 100644 index 0000000..3c34652 --- /dev/null +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_SelectInitialAffix.cs @@ -0,0 +1,31 @@ +using MikuSB.GameServer.Game.Support; +using MikuSB.Proto; +using System.Text.Json; + +namespace MikuSB.GameServer.Server.CallGS.Handlers.SupporterCard; + +[CallGSApi("SupporterCard_SelectInitialAffix")] +public class SupporterCard_SelectInitialAffix : ICallGSHandler +{ + public async Task Handle(Connection connection, string param, ushort seqNo) + { + var req = JsonSerializer.Deserialize(param); + var card = req == null ? null : connection.Player!.InventoryManager.GetSupportCardItem((uint)req.SupportCardUid); + if (req == null || card == null || req.Index is < 1 or > 2 || card.AffixId != req.Index || !SupportAffixStateService.HasAffix(card, SupportAffixStateService.PendingInitialAffixSlot)) + { + await SupporterCardAffixShared.SendSelectResponse(connection); + return; + } + + if (req.SelectNew) + SupportAffixStateService.CopyAffix(card, SupportAffixStateService.PendingInitialAffixSlot, req.Index); + + SupportAffixStateService.ClearAffix(card, SupportAffixStateService.PendingInitialAffixSlot); + card.AffixId = 0; + + var sync = new NtfSyncPlayer(); + sync.Items.Add(card.ToProto()); + SupporterCardAffixShared.Save(connection); + await SupporterCardAffixShared.SendSelectResponse(connection, sync); + } +} diff --git a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs index 4b08f99..d259cfa 100644 --- a/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs +++ b/GameServer/Server/CallGS/Handlers/SupporterCard/SupporterCard_Upgrade.cs @@ -86,17 +86,15 @@ public class SupporterCard_Upgrade : ICallGSHandler // Unlock next affix slot when reaching max level for the first time if (supportCardExcel != null) { - var currentSlots = supportCard.Affixs.Count / 2; + var currentSlots = Enumerable.Range(1, SupportAffixStateService.ActiveThirdAffixSlot) + .Count(slot => SupportAffixStateService.HasAffix(supportCard, slot)); 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); - } + SupportAffixStateService.SetAffix(supportCard, currentSlots + 1, affixId, tier); } } } diff --git a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs index 1b25602..7e65044 100644 --- a/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs +++ b/GameServer/Server/Packet/Send/Misc/PacketNtfCallScript.cs @@ -1,6 +1,8 @@ -using MikuSB.Database.Character; +using MikuSB.Database.Character; using MikuSB.Database.Inventory; +using MikuSB.Enums.Item; using MikuSB.GameServer.Game.Player; +using MikuSB.GameServer.Game.Support; using MikuSB.Proto; using MikuSB.TcpSharp; @@ -10,14 +12,14 @@ public class PacketNtfCallScript : BasePacket { public PacketNtfCallScript(List characters) : base(CmdIds.NtfScript) { - var proto = new NtfCallScript - { + var proto = new NtfCallScript + { Api = "", Arg = "{}", ExtraSync = new NtfSyncPlayer { Items = { characters.Select(x => x.ToProto()) } - } + } }; SetData(proto); @@ -61,7 +63,7 @@ public class PacketNtfCallScript : BasePacket Arg = "{}", ExtraSync = new NtfSyncPlayer { - Items = { cards.Select(x => x.ToProto()) } + Items = { cards.Select(ToSupportCardProto) } } }; @@ -95,7 +97,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()); + foreach (var supportCard in inventory.SupportCards.Values) extraSync.Items.Add(ToSupportCardProto(supportCard)); proto.ExtraSync = extraSync; SetData(proto); } @@ -128,4 +130,12 @@ public class PacketNtfCallScript : BasePacket SetData(proto); } + + private static Item ToSupportCardProto(GameSupportCardInfo card) + { + SupportAffixStateService.NormalizePendingState(card); + var proto = card.ToProto(); + proto.Slots[(uint)ItemSupportCardSlotTypeEnum.SLOT_AFFIXINDEX] = SupportAffixStateService.GetVisibleInitialAffixIndex(card); + return proto; + } } diff --git a/version.txt b/version.txt index 1c47632..9fd0a3d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v=3.1 \ No newline at end of file +v=3.2 \ No newline at end of file