fix(tutorial): gift_receive reward_list carries post-state totals, not deltas

The client's PlayerStaticData.UpdateHaveUserGoodsNumByJsonData does direct
assignment on each reward_list entry's reward_num, so currency/item totals
must be the new viewer balance — not the gift delta. Fresh accounts were
seeing their cached crystal/rupy balances clobbered down to the gift counts
until the next /load/index. Matches the project_wire_reward_list_post_state
memory and the prod capture (which shows 120 rupy = baseline 20 + gift 100).
This commit is contained in:
gamer147
2026-05-28 13:16:51 -04:00
parent 5d8a6626bb
commit 190b50cbaf
2 changed files with 40 additions and 1 deletions

View File

@@ -154,19 +154,47 @@ public class GiftController : SVSimController
.Select(p => Clone(p, nowString))
.ToList(),
IsUnreceivedPresent = false,
// reward_list entries must carry POST-STATE TOTALS, not gift deltas.
// The client's PlayerStaticData.UpdateHaveUserGoodsNumByJsonData does direct
// assignment on each entry's reward_num — emitting the delta would clobber
// the client-side cached balance down to the gift amount until the next /load/index.
// See project memory: project_wire_reward_list_post_state.
RewardList = TutorialGifts
.Where(p => requestedIds.Contains(p.PresentId))
.Select(p => new GiftRewardListEntry
{
RewardType = p.RewardType,
RewardId = p.RewardDetailId,
RewardNum = p.RewardCount,
RewardNum = ResolvePostStateRewardNum(p, viewer),
})
.ToList(),
TutorialStep = 41,
};
}
/// <summary>
/// Returns the post-grant viewer balance for the given gift entry, not the gift delta.
/// reward_list on wire carries post-state totals (client does direct assignment).
/// </summary>
private static string ResolvePostStateRewardNum(PresentDto gift, SVSim.Database.Models.Viewer viewer)
{
switch (gift.RewardType)
{
case "1": // Crystal
return ((long)viewer.Currency.Crystals).ToString(System.Globalization.CultureInfo.InvariantCulture);
case "9": // Rupy
return ((long)viewer.Currency.Rupees).ToString(System.Globalization.CultureInfo.InvariantCulture);
case "4": // Item
{
int itemId = int.Parse(gift.RewardDetailId, System.Globalization.CultureInfo.InvariantCulture);
var owned = viewer.Items.FirstOrDefault(i => i.Item.Id == itemId);
return ((long)(owned?.Count ?? 0)).ToString(System.Globalization.CultureInfo.InvariantCulture);
}
default:
return gift.RewardCount; // unknown type — fall back to gift count (better than 0)
}
}
private static UserGoodsType WireRewardTypeToUserGoodsType(int wireType) => wireType switch
{
1 => UserGoodsType.Crystal,