feat(home-dialog): populate home_dialog_list on /mypage/index

Walk-down behavior: each call emits the highest-priority unfired
active dialog; subsequent calls walk to the next-priority entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-08 18:55:48 -04:00
parent 7e757ebcd2
commit 9d6a5cc3b9
4 changed files with 233 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ using SVSim.EmulatedEntrypoint.Infrastructure;
using SVSim.EmulatedEntrypoint.Models.Dtos;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Controllers;
@@ -24,14 +25,17 @@ public class MyPageController : SVSimController
private readonly IGlobalsRepository _globalsRepository;
private readonly IGameConfigService _config;
private readonly IArenaTwoPickRunRepository _arenaTwoPickRuns;
private readonly IHomeDialogSessionTracker _homeDialogTracker;
public MyPageController(IViewerRepository viewerRepository, IGlobalsRepository globalsRepository,
IGameConfigService config, IArenaTwoPickRunRepository arenaTwoPickRuns)
IGameConfigService config, IArenaTwoPickRunRepository arenaTwoPickRuns,
IHomeDialogSessionTracker homeDialogTracker)
{
_viewerRepository = viewerRepository;
_globalsRepository = globalsRepository;
_config = config;
_arenaTwoPickRuns = arenaTwoPickRuns;
_homeDialogTracker = homeDialogTracker;
}
[HttpPost("index")]
@@ -59,6 +63,17 @@ public class MyPageController : SVSimController
var masterPointPeriod = await _globalsRepository.GetCurrentMasterPointPeriod();
var bannerEntries = await _globalsRepository.GetBanners();
var specialDeckFormats = await _globalsRepository.GetActiveSpecialDeckFormats();
var activeHomeDialogs = await _globalsRepository.GetActiveHomeDialogsAsync(DateTime.UtcNow);
var homeDialogList = new List<Models.Dtos.Common.HomeDialog>();
foreach (var entry in activeHomeDialogs)
{
if (_homeDialogTracker.TryReserve(viewer.ShortUdid, entry.Id))
{
homeDialogList.Add(BuildHomeDialog(entry));
break; // Client only reads [0]; emit at most one per call.
}
}
// Remaining stubs are tagged TODO(mypage-stub) — see docs/api-spec/endpoints/post-login/mypage-index.md.
return new MyPageIndexResponse
@@ -110,6 +125,7 @@ public class MyPageController : SVSimController
// out is_hide=1 tutorial packs (the legendary starter 99047) via PackConfig.EnableBuyPack.
// Populate from viewer.Items so the client's dict stays in sync with the DB.
UserItemList = viewer.Items.Select(i => new UserItem(i)).ToList(),
HomeDialogList = homeDialogList,
SpecialCrystalInfo = new(), // TODO(mypage-stub): same shape/source as /load/index
// CompetitionInfo, ShopNotification, StoryNotification, GuildNotification, GatheringInfo,
// IsHiddenBossAppeared, SubBanner/SubBannerList/HomeDialogList/UserOfflineEvent/UserItemList,
@@ -268,6 +284,32 @@ public class MyPageController : SVSimController
};
}
/// <summary>
/// Deserializes the jsonb button_list column into wire-shape DTOs. Truncates >3 buttons —
/// the client's switch in MyPageHomeDialog.InitializeButtonAction only handles 0/1/2/3,
/// extras would be silently ignored anyway; doing it server-side keeps the wire honest.
/// </summary>
private static Models.Dtos.Common.HomeDialog BuildHomeDialog(HomeDialogEntry row)
{
List<Models.Dtos.Common.HomeDialogButtonDto> buttons = new();
if (!string.IsNullOrEmpty(row.ButtonListJson) && row.ButtonListJson != "[]")
{
buttons = JsonSerializer.Deserialize<List<Models.Dtos.Common.HomeDialogButtonDto>>(
row.ButtonListJson, JsonbReadOptions.Instance) ?? new();
}
if (buttons.Count > 3)
{
buttons = buttons.Take(3).ToList();
}
return new Models.Dtos.Common.HomeDialog
{
Type = row.Type?.ToString(CultureInfo.InvariantCulture),
TitleTextId = row.TitleTextId,
Image = row.Image,
ButtonList = buttons,
};
}
private static BannerInfo BuildBannerInfo(BannerEntry row)
{
List<string> imagePaths = new();

View File

@@ -0,0 +1,37 @@
using System.Text.Json.Serialization;
using MessagePack;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Common;
/// <summary>
/// One entry in /mypage/index data.home_dialog_list. Client parser
/// (Wizard/MyPageHomeDialogData.cs) only reads [0]; up to 3 buttons supported
/// (switch on 0/1/2/3 in MyPageHomeDialog.cs).
/// </summary>
[MessagePackObject]
public class HomeDialog
{
/// <summary>Wire "type" — prod sends "1"; client parser ignores it. Stringly-typed.
/// Null is omitted by the global WhenWritingNull policy.</summary>
[JsonPropertyName("type")] [Key("type")] public string? Type { get; set; }
/// <summary>Localization key resolved client-side via Data.SystemText.Get.</summary>
[JsonPropertyName("title_text_id")] [Key("title_text_id")] public string TitleTextId { get; set; } = string.Empty;
/// <summary>Asset name resolved via ResourcesManager.AssetLoadPathType.UiDownLoad.</summary>
[JsonPropertyName("image")] [Key("image")] public string Image { get; set; } = string.Empty;
[JsonPropertyName("button_list")] [Key("button_list")] public List<HomeDialogButtonDto> ButtonList { get; set; } = new();
}
[MessagePackObject]
public class HomeDialogButtonDto
{
[JsonPropertyName("button_text_id")] [Key("button_text_id")] public string ButtonTextId { get; set; } = string.Empty;
/// <summary>Scene id consumed by MyPageBannerBase.SceneChangeBySetting (e.g. "card_pack", "mission").</summary>
[JsonPropertyName("scene")] [Key("scene")] public string Scene { get; set; } = string.Empty;
/// <summary>Contextual id passed to the scene (e.g. parent_gacha_id "80032"). Stringly-typed on the wire.</summary>
[JsonPropertyName("status")] [Key("status")] public string Status { get; set; } = string.Empty;
}

View File

@@ -123,7 +123,7 @@ public class MyPageIndexResponse
[JsonPropertyName("home_dialog_list")]
[Key("home_dialog_list")]
public List<object> HomeDialogList { get; set; } = new();
public List<Common.HomeDialog> HomeDialogList { get; set; } = new();
// ── Room type in session (Special-format windows) ──────────────────────