From a8bbc39bfdbe60d560f6e810a5bd04098e3681d1 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Fri, 29 May 2026 00:39:50 -0400 Subject: [PATCH] fix(pack): emit one gacha-point entry per emblem cosmetic + clean stale docstring --- .../Services/GachaPointService.cs | 37 ++++++++++------ .../Services/IGachaPointService.cs | 2 +- .../Services/GachaPointServiceTests.cs | 43 +++++++++++++++++++ 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/SVSim.EmulatedEntrypoint/Services/GachaPointService.cs b/SVSim.EmulatedEntrypoint/Services/GachaPointService.cs index a8a07df..89975c9 100644 --- a/SVSim.EmulatedEntrypoint/Services/GachaPointService.cs +++ b/SVSim.EmulatedEntrypoint/Services/GachaPointService.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.EntityFrameworkCore; using SVSim.Database; using SVSim.Database.Enums; @@ -58,11 +59,11 @@ public sealed class GachaPointService : IGachaPointService .OrderBy(c => c.Class?.Id ?? 0).ThenBy(c => c.Id)) { if (!cosmeticLookup.TryGetValue(card.Id, out var cosmetics)) continue; - var emblem = cosmetics.FirstOrDefault(c => c.Type == CosmeticType.Emblem); + var emblems = cosmetics.Where(c => c.Type == CosmeticType.Emblem).ToList(); var skin = cosmetics.FirstOrDefault(c => c.Type == CosmeticType.Skin); - if (emblem is null) continue; // every gacha-point entry has an emblem + if (emblems.Count == 0) continue; // every gacha-point entry has at least one emblem - var classId = (card.Class?.Id ?? 0).ToString(); + var classId = (card.Class?.Id ?? 0).ToString(CultureInfo.InvariantCulture); var isReceived = receivedCardIds.Contains(card.Id); if (IsLeaderCard(skin)) @@ -70,7 +71,11 @@ public sealed class GachaPointService : IGachaPointService // Leader card — 3 entries in capture order: Sleeve/Card-cosmetic (type 6), // Skin (type 10), Emblem (type 7). The reward_type=6 entry's detail id is the // card_id itself, mirroring the prod capture exactly (no Sleeve cosmetic row - // is required — synthesizing from card.Id is robust to missing rows). + // is required — synthesizing from card.Id is robust to missing rows). For the + // emblem we take the first row only; no capture has shown leader cards with + // multiple emblems, and the verified shape is exactly 1 Sleeve + 1 Skin + 1 + // Emblem. Revisit if such a capture lands. + var emblem = emblems[0]; leader.Add(new GachaPointRewardDto { ClassId = classId, CardId = card.Id, IsReceived = isReceived, @@ -95,19 +100,23 @@ public sealed class GachaPointService : IGachaPointService } else { - standard.Add(new GachaPointRewardDto + // Standard legendaries can have multiple emblem cosmetics (e.g. prod capture + // pack 10008 card 108044010 has emblem ids 900041040 and 900041050). Emit one + // reward_list entry per emblem. + var dto = new GachaPointRewardDto { ClassId = classId, CardId = card.Id, IsReceived = isReceived, - RewardList = + }; + foreach (var emblem in emblems) + { + dto.RewardList.Add(new GachaPointRewardDetailEntry { - new GachaPointRewardDetailEntry - { - RewardType = (int)UserGoodsType.Emblem, - RewardDetailId = emblem.CosmeticId, - RewardNumber = 1, - }, - }, - }); + RewardType = (int)UserGoodsType.Emblem, + RewardDetailId = emblem.CosmeticId, + RewardNumber = 1, + }); + } + standard.Add(dto); } } diff --git a/SVSim.EmulatedEntrypoint/Services/IGachaPointService.cs b/SVSim.EmulatedEntrypoint/Services/IGachaPointService.cs index b5681cf..7d89b5a 100644 --- a/SVSim.EmulatedEntrypoint/Services/IGachaPointService.cs +++ b/SVSim.EmulatedEntrypoint/Services/IGachaPointService.cs @@ -16,7 +16,7 @@ public interface IGachaPointService /// /// Increment the viewer's balance for by - /// child.OverrideIncreaseGachaPoint ?? pack.GachaPointConfig.IncreaseGachaPoint + /// child.OverrideIncreaseGachaPoint > 0 ? child.OverrideIncreaseGachaPoint : pack.GachaPointConfig.IncreaseGachaPoint /// times . No-op when the pack lacks a GachaPointConfig. /// Caller is responsible for SaveChangesAsync. /// diff --git a/SVSim.UnitTests/Services/GachaPointServiceTests.cs b/SVSim.UnitTests/Services/GachaPointServiceTests.cs index c545a55..0434cb8 100644 --- a/SVSim.UnitTests/Services/GachaPointServiceTests.cs +++ b/SVSim.UnitTests/Services/GachaPointServiceTests.cs @@ -144,6 +144,49 @@ public class GachaPointServiceTests Assert.That(leaderEntry.RewardList[2].RewardDetailId, Is.EqualTo(704141010)); } + [Test] + public async Task GetRewards_emits_one_entry_per_emblem_for_cards_with_multiple_emblems() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var set = new ShadowverseCardSetEntry { Id = 10008, IsInRotation = true }; + db.CardSets.Add(set); + var leg = new ShadowverseCardEntry + { + Id = 108044010, Name = "leg-multi-emblem", Rarity = Rarity.Legendary, + Class = null, IsFoil = false, + }; + set.Cards.Add(leg); + + // Two emblems for one card — matches prod capture pack 10008 card 108044010. + db.CardCosmeticRewards.AddRange( + new CardCosmeticReward { CardId = 108044010, Type = CosmeticType.Emblem, CosmeticId = 900041040 }, + new CardCosmeticReward { CardId = 108044010, Type = CosmeticType.Emblem, CosmeticId = 900041050 }); + + db.Packs.Add(new PackConfigEntry + { + Id = 10008, BasePackId = 10008, PackCategory = PackCategory.LegendCardPack, + CommenceDate = DateTime.UtcNow.AddDays(-1), CompleteDate = DateTime.UtcNow.AddDays(30), + GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 }, + }); + await db.SaveChangesAsync(); + + var svc = scope.ServiceProvider.GetRequiredService(); + var result = await svc.GetRewardsAsync(10008, viewerId); + + Assert.That(result, Has.Count.EqualTo(1)); + var entry = result[0]; + Assert.That(entry.CardId, Is.EqualTo(108044010)); + Assert.That(entry.RewardList, Has.Count.EqualTo(2), + "cards with multiple emblems must emit one reward_list entry per emblem"); + var detailIds = entry.RewardList.Select(r => r.RewardDetailId).OrderBy(x => x).ToList(); + Assert.That(detailIds, Is.EqualTo(new[] { 900041040L, 900041050L })); + Assert.That(entry.RewardList.All(r => r.RewardType == 7), Is.True); + } + [Test] public async Task GetRewards_marks_already_received_cards() {