From 1e2e18e828787de4c65943d3bf4bf2623a4f8105 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 31 May 2026 12:56:05 -0400 Subject: [PATCH] fix(tk2): rewards array uses ReceivedReward shape (reward_detail_id/item_type/is_usable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /retire and /finish responses carry two reward arrays with DIFFERENT key schemas: rewards[] → ReceivedReward(JsonData) parser {reward_type, reward_detail_id, item_type, reward_count?, is_usable} reward_list[] → PlayerStaticData.UpdateHaveUserGoodsNumByJsonData {reward_type, reward_id, reward_num} We were emitting both with reward_list's schema, so the client threw KeyNotFoundException on `data["reward_detail_id"]` while parsing each delta entry — observed live as the retire-screen failure. - New TwoPickRewardReceivedDto mirrors the existing Achievement/ TotalReceiveCountDto shape. - FinishResponseDto.Rewards switched from List to List. - GrantRunRewardsAndDeleteAsync pre-loads ItemEntry.Type for any Item-typed reward so item_type ships correctly (0 for currencies). - Existing tests renamed RewardNum→RewardCount on the deltas list. Co-Authored-By: Claude Opus 4.7 --- .../ArenaTwoPick/FinishResponseDto.cs | 36 +++++++++++++++++-- .../Services/ArenaTwoPickService.cs | 24 +++++++++++-- .../ArenaTwoPickServiceFinishTests.cs | 6 ++-- 3 files changed, 58 insertions(+), 8 deletions(-) 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); }