diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/ArenaTwoPick/FinishResponseDto.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/ArenaTwoPick/FinishResponseDto.cs index 4ac0a13..45a7564 100644 --- a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/ArenaTwoPick/FinishResponseDto.cs +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/ArenaTwoPick/FinishResponseDto.cs @@ -7,11 +7,43 @@ namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.ArenaTwoPick; [MessagePackObject] public class FinishResponseDto { - /// Per-grant deltas — drives "+N received" popup. + /// + /// Per-grant deltas — drives the "+N received" popup. Parsed by the client via + /// ReceivedReward(JsonData) (Shadowverse_Code/ReceivedReward.cs:25) which expects + /// the {reward_type, reward_detail_id, item_type, reward_count?, is_usable} shape — distinct + /// from the RewardEntryDto shape used by . + /// [JsonPropertyName("rewards")] [Key("rewards")] - public List Rewards { get; set; } = new(); + public List Rewards { get; set; } = new(); /// Post-state totals — drives PlayerStaticData.UpdateHaveUserGoodsNumByJsonData. [JsonPropertyName("reward_list")] [Key("reward_list")] public List RewardList { get; set; } = new(); } + +/// +/// Wire shape parsed by Shadowverse_Code/ReceivedReward.cs ctor. Used in the +/// rewards arrays of /arena_two_pick/{retire,finish}. +/// +[MessagePackObject] +public class TwoPickRewardReceivedDto +{ + [JsonPropertyName("reward_type")] [Key("reward_type")] + public int RewardType { get; set; } + + [JsonPropertyName("reward_detail_id")] [Key("reward_detail_id")] + public long RewardDetailId { get; set; } + + [JsonPropertyName("reward_count")] [Key("reward_count")] + public int RewardCount { get; set; } + + /// + /// Item-master item_type enum (1=challenge ticket, 2=card-pack ticket, …) for Item-typed + /// rewards. 0 for currencies (Crystal/Rupy/RedEther) — the client only reads this for Items. + /// + [JsonPropertyName("item_type")] [Key("item_type")] + public int ItemType { get; set; } + + [JsonPropertyName("is_usable")] [Key("is_usable")] + public bool IsUsable { get; set; } = true; +} diff --git a/SVSim.EmulatedEntrypoint/Services/ArenaTwoPickService.cs b/SVSim.EmulatedEntrypoint/Services/ArenaTwoPickService.cs index 32d6581..e21787c 100644 --- a/SVSim.EmulatedEntrypoint/Services/ArenaTwoPickService.cs +++ b/SVSim.EmulatedEntrypoint/Services/ArenaTwoPickService.cs @@ -297,13 +297,31 @@ public class ArenaTwoPickService : IArenaTwoPickService var rewardRows = await _rewards.GetRewardsByWinCountAsync(run.WinCount); var viewer = await LoadViewerForGrantsAsync(viewerId); - var deltas = new List(); + // Pre-load item_type for any Item-typed reward so we can populate it on the + // per-grant delta entries. Currencies don't need a lookup (item_type stays 0). + var itemRewardIds = rewardRows + .Where(r => r.RewardType == (int)SVSim.Database.Enums.UserGoodsType.Item) + .Select(r => (int)r.RewardId) + .Distinct() + .ToList(); + var itemTypeById = itemRewardIds.Count == 0 + ? new Dictionary() + : await _db.Items.Where(i => itemRewardIds.Contains(i.Id)) + .ToDictionaryAsync(i => i.Id, i => i.Type); + + var deltas = new List(); foreach (var r in rewardRows) { var goodsType = (SVSim.Database.Enums.UserGoodsType)r.RewardType; await _grants.ApplyAsync(viewer, goodsType, r.RewardId, r.RewardNum); - // Rewards = deltas (per-grant amounts), not post-state totals. - deltas.Add(new RewardEntryDto { RewardType = r.RewardType, RewardId = r.RewardId, RewardNum = r.RewardNum }); + deltas.Add(new TwoPickRewardReceivedDto + { + RewardType = r.RewardType, + RewardDetailId = r.RewardId, + RewardCount = r.RewardNum, + ItemType = itemTypeById.TryGetValue((int)r.RewardId, out var t) ? t : 0, + IsUsable = true, + }); } await _db.SaveChangesAsync(); diff --git a/SVSim.UnitTests/Services/ArenaTwoPickServiceFinishTests.cs b/SVSim.UnitTests/Services/ArenaTwoPickServiceFinishTests.cs index c2bb8fc..b50d066 100644 --- a/SVSim.UnitTests/Services/ArenaTwoPickServiceFinishTests.cs +++ b/SVSim.UnitTests/Services/ArenaTwoPickServiceFinishTests.cs @@ -108,8 +108,8 @@ public class ArenaTwoPickServiceFinishTests var dto = await svc.RetireAsync(vid); Assert.That(dto.Rewards.Count, Is.EqualTo(2)); - Assert.That(dto.Rewards.Single(r => r.RewardType == 4).RewardNum, Is.EqualTo(1)); - Assert.That(dto.Rewards.Single(r => r.RewardType == 9).RewardNum, Is.EqualTo(700)); + Assert.That(dto.Rewards.Single(r => r.RewardType == 4).RewardCount, Is.EqualTo(1)); + Assert.That(dto.Rewards.Single(r => r.RewardType == 9).RewardCount, Is.EqualTo(700)); Assert.That(dto.RewardList.Single(r => r.RewardType == 4).RewardNum, Is.EqualTo(6), "5 + 1"); Assert.That(dto.RewardList.Single(r => r.RewardType == 9).RewardNum, Is.EqualTo(750), "50 + 700"); @@ -136,7 +136,7 @@ public class ArenaTwoPickServiceFinishTests await using var _ = db; var dto = await svc.FinishAsync(vid); - Assert.That(dto.Rewards.Single(r => r.RewardType == 9).RewardNum, Is.EqualTo(100)); + Assert.That(dto.Rewards.Single(r => r.RewardType == 9).RewardCount, Is.EqualTo(100)); Assert.That(await db.ViewerArenaTwoPickRuns.AnyAsync(), Is.False); }