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);
}