Things were working, suddenly regressed
This commit is contained in:
@@ -1,28 +1,49 @@
|
||||
using MessagePack;
|
||||
using SVSim.Database.Enums;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Current arena season config. Shape derived from 2026-05-23 prod capture
|
||||
/// (<c>arena_info[0].format_info</c>).
|
||||
///
|
||||
/// Wire mixes types: <c>two_pick_type</c> and <c>last_card_pack_set_id</c> are strings
|
||||
/// (PHP-backend stringification), <c>announce_id</c> is an int, and the times use
|
||||
/// space-separated "yyyy-MM-dd HH:mm:ss" rather than ISO. Numeric-typed properties use
|
||||
/// <c>AllowReadingFromString</c> on the controller's JsonSerializerOptions so string-quoted
|
||||
/// ints deserialize cleanly out of the seeded jsonb.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class ArenaFormatInfo
|
||||
{
|
||||
/// <summary>PickTwoFormat as int (0=None,1=Normal,2=Backdraft,3=Cube,4=Chaos,...).</summary>
|
||||
[JsonPropertyName("two_pick_type")]
|
||||
[Key("two_pick_type")]
|
||||
public PickTwoFormat PickTwoFormat { get; set; }
|
||||
public int TwoPickType { get; set; }
|
||||
|
||||
[JsonPropertyName("card_pool_name")]
|
||||
[Key("card_pool_name")]
|
||||
public string CardPoolName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("announce_id")]
|
||||
[Key("announce_id")]
|
||||
public string AnnounceId { get; set; } = "0";
|
||||
[JsonPropertyName("card_pool_url")]
|
||||
[Key("card_pool_url")]
|
||||
public string CardPoolUrl { get; set; } = string.Empty;
|
||||
public int AnnounceId { get; set; }
|
||||
|
||||
/// <summary>The current card pack set id, e.g. "10029". String on the wire.</summary>
|
||||
[JsonPropertyName("last_card_pack_set_id")]
|
||||
[Key("last_card_pack_set_id")]
|
||||
public string LastCardPackSetId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Wire format is "yyyy-MM-dd HH:mm:ss" (space-separated, prod's PHP convention) — NOT ISO.
|
||||
/// Stored as string here so the jsonb passthrough survives byte-for-byte; the client's
|
||||
/// DateTime.Parse accepts either format on the receiving side.
|
||||
/// </summary>
|
||||
[JsonPropertyName("start_time")]
|
||||
[Key("start_time")]
|
||||
public DateTime StartTime { get; set; }
|
||||
public string StartTime { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("end_time")]
|
||||
[Key("end_time")]
|
||||
public DateTime EndTime { get; set; }
|
||||
}
|
||||
public string EndTime { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,12 @@ public class AvatarInfo
|
||||
[JsonPropertyName("abilities")]
|
||||
[Key("abilities")]
|
||||
public Dictionary<string, AvatarAbility> Abilities { get; set; } = new Dictionary<string, AvatarAbility>();
|
||||
/// <summary>
|
||||
/// Prod (2026-05-23) sends an empty array here. Distinct shape from MyRotationInfo.Schedules,
|
||||
/// which is a dict {free_battle, gathering}. Entry shape TBD when an active Avatar season is
|
||||
/// captured — see <see cref="AvatarSchedule"/>.
|
||||
/// </summary>
|
||||
[JsonPropertyName("schedules")]
|
||||
[Key("schedules")]
|
||||
public SpecialRotationSchedule Schedules { get; set; } = new SpecialRotationSchedule();
|
||||
public List<AvatarSchedule> Schedules { get; set; } = new();
|
||||
}
|
||||
13
SVSim.EmulatedEntrypoint/Models/Dtos/AvatarSchedule.cs
Normal file
13
SVSim.EmulatedEntrypoint/Models/Dtos/AvatarSchedule.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder for an Avatar/Hero mode schedule entry. The 2026-05-23 prod capture had an empty
|
||||
/// schedules list, so the entry shape is TBD — fill in fields when an active Avatar window is
|
||||
/// captured. AvatarBattleAllInfo.Parse on the client side is the parser to read for shape.
|
||||
/// </summary>
|
||||
[MessagePackObject(keyAsPropertyName: true)]
|
||||
public class AvatarSchedule
|
||||
{
|
||||
}
|
||||
16
SVSim.EmulatedEntrypoint/Models/Dtos/BasicPuzzle.cs
Normal file
16
SVSim.EmulatedEntrypoint/Models/Dtos/BasicPuzzle.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// basic_puzzle.is_display_badge — drives the "practice puzzle" badge on the
|
||||
/// footer. Read by MyPageTask.cs:177.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class BasicPuzzle
|
||||
{
|
||||
[JsonPropertyName("is_display_badge")]
|
||||
[Key("is_display_badge")]
|
||||
public bool IsDisplayBadge { get; set; }
|
||||
}
|
||||
43
SVSim.EmulatedEntrypoint/Models/Dtos/ColosseumInfo.cs
Normal file
43
SVSim.EmulatedEntrypoint/Models/Dtos/ColosseumInfo.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// colosseum_info on /mypage/index, consumed by
|
||||
/// ColosseumEntryInfoTask.SetColosseumInfo (Wizard/ColosseumEntryInfoTask.cs:99).
|
||||
///
|
||||
/// The block is indexed unconditionally — it MUST be present, and
|
||||
/// `is_colosseum_period` MUST be set. All other fields are only read inside the
|
||||
/// `if (IsColosseumPeriod)` branch, so when no Take Two cup is active we emit
|
||||
/// the minimum payload (is_colosseum_period=false) and leave the rest defaulted.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class ColosseumInfo
|
||||
{
|
||||
[JsonPropertyName("is_colosseum_period")]
|
||||
[Key("is_colosseum_period")]
|
||||
public bool IsColosseumPeriod { get; set; }
|
||||
|
||||
[JsonPropertyName("is_round_period")]
|
||||
[Key("is_round_period")]
|
||||
public bool IsRoundPeriod { get; set; }
|
||||
|
||||
[JsonPropertyName("deck_format")]
|
||||
[Key("deck_format")]
|
||||
public int DeckFormat { get; set; }
|
||||
|
||||
/// <summary>Wire is "1"/"0" string in prod. Client compares with == "1".</summary>
|
||||
[JsonPropertyName("is_normal_two_pick")]
|
||||
[Key("is_normal_two_pick")]
|
||||
public string IsNormalTwoPick { get; set; } = "0";
|
||||
|
||||
/// <summary>Used as ColorCodeId (stringified int).</summary>
|
||||
[JsonPropertyName("is_special_mode")]
|
||||
[Key("is_special_mode")]
|
||||
public string IsSpecialMode { get; set; } = "0";
|
||||
|
||||
[JsonPropertyName("colosseum_name")]
|
||||
[Key("colosseum_name")]
|
||||
public string ColosseumName { get; set; } = string.Empty;
|
||||
}
|
||||
29
SVSim.EmulatedEntrypoint/Models/Dtos/Convention.cs
Normal file
29
SVSim.EmulatedEntrypoint/Models/Dtos/Convention.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Convention/offline-event participation block returned by /mypage/index.
|
||||
/// Client reads is_join_tournament, recent_start_date (null-checked, optional),
|
||||
/// and is_admin_watch_user. See MyPageTask.cs:58-63.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class Convention
|
||||
{
|
||||
[JsonPropertyName("is_join_tournament")]
|
||||
[Key("is_join_tournament")]
|
||||
public bool IsJoinTournament { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ISO datetime. Optional — omitted via WhenWritingNull when not set.
|
||||
/// Client null-checks before parsing (MyPageTask.cs:59).
|
||||
/// </summary>
|
||||
[JsonPropertyName("recent_start_date")]
|
||||
[Key("recent_start_date")]
|
||||
public string? RecentStartDate { get; set; }
|
||||
|
||||
[JsonPropertyName("is_admin_watch_user")]
|
||||
[Key("is_admin_watch_user")]
|
||||
public bool IsAdminWatchUser { get; set; }
|
||||
}
|
||||
38
SVSim.EmulatedEntrypoint/Models/Dtos/DefaultDeck.cs
Normal file
38
SVSim.EmulatedEntrypoint/Models/Dtos/DefaultDeck.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// One of the eight "starter" decks (one per class), as surfaced under
|
||||
/// <c>/deck/info data.default_deck_list</c>. Wire shape derived from 2026-05-23 prod capture.
|
||||
/// Used by the client both as new-account defaults and as the source for "use default deck".
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class DefaultDeck
|
||||
{
|
||||
[JsonPropertyName("deck_no")]
|
||||
[Key("deck_no")]
|
||||
public int DeckNo { get; set; }
|
||||
|
||||
[JsonPropertyName("class_id")]
|
||||
[Key("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
|
||||
[JsonPropertyName("sleeve_id")]
|
||||
[Key("sleeve_id")]
|
||||
public long SleeveId { get; set; }
|
||||
|
||||
[JsonPropertyName("leader_skin_id")]
|
||||
[Key("leader_skin_id")]
|
||||
public int LeaderSkinId { get; set; }
|
||||
|
||||
[JsonPropertyName("deck_name")]
|
||||
[Key("deck_name")]
|
||||
public string DeckName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>40 card_id values — same card may repeat (max 3 per card per Shadowverse rules).</summary>
|
||||
[JsonPropertyName("card_id_array")]
|
||||
[Key("card_id_array")]
|
||||
public List<long> CardIdArray { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Default leader skin per class (8 entries — one per class). Surfaced under
|
||||
/// <c>/deck/info data.user_leader_skin_setting_list</c>. Despite the <c>user_</c> prefix on the
|
||||
/// wire, this is GLOBAL data (same for every viewer) — naming is the client's, not ours.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class DefaultLeaderSkinSetting
|
||||
{
|
||||
[JsonPropertyName("class_id")]
|
||||
[Key("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
|
||||
[JsonPropertyName("is_random_leader_skin")]
|
||||
[Key("is_random_leader_skin")]
|
||||
public int IsRandomLeaderSkin { get; set; }
|
||||
|
||||
[JsonPropertyName("leader_skin_id")]
|
||||
[Key("leader_skin_id")]
|
||||
public int LeaderSkinId { get; set; }
|
||||
}
|
||||
31
SVSim.EmulatedEntrypoint/Models/Dtos/GuildNotification.cs
Normal file
31
SVSim.EmulatedEntrypoint/Models/Dtos/GuildNotification.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// guild_notification on /mypage/index. Consumed by
|
||||
/// MyPageNotifications.GuildNotification.SetGuildNotification. Prod sends nulls
|
||||
/// for guild_id / guild_room_message_id when the viewer isn't in a guild; with
|
||||
/// WhenWritingNull those keys are omitted on our wire, which is equivalent
|
||||
/// since the parser is null-tolerant.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class GuildNotification
|
||||
{
|
||||
[JsonPropertyName("guild_id")]
|
||||
[Key("guild_id")]
|
||||
public long? GuildId { get; set; }
|
||||
|
||||
[JsonPropertyName("guild_room_message_id")]
|
||||
[Key("guild_room_message_id")]
|
||||
public long? GuildRoomMessageId { get; set; }
|
||||
|
||||
[JsonPropertyName("is_join_request")]
|
||||
[Key("is_join_request")]
|
||||
public bool IsJoinRequest { get; set; }
|
||||
|
||||
[JsonPropertyName("is_invited")]
|
||||
[Key("is_invited")]
|
||||
public bool IsInvited { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Master Points season window. Client only reads end_time at /mypage/index
|
||||
/// (MyPageTask.cs:113-114) when _masterResetNextTime hasn't been set yet, but
|
||||
/// prod also emits id / period_num / necessary_score / begin_time — we mirror
|
||||
/// them so the wire shape matches.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class MasterPointRankingPeriod
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
[Key("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("period_num")]
|
||||
[Key("period_num")]
|
||||
public int PeriodNum { get; set; }
|
||||
|
||||
[JsonPropertyName("necessary_score")]
|
||||
[Key("necessary_score")]
|
||||
public int NecessaryScore { get; set; }
|
||||
|
||||
/// <summary>ISO datetime.</summary>
|
||||
[JsonPropertyName("begin_time")]
|
||||
[Key("begin_time")]
|
||||
public string BeginTime { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>ISO datetime. Required — client calls DateTime.Parse on it.</summary>
|
||||
[JsonPropertyName("end_time")]
|
||||
[Key("end_time")]
|
||||
public string EndTime { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -15,8 +15,9 @@ public class MyRotationInfo
|
||||
[JsonPropertyName("setting")]
|
||||
[Key("setting")]
|
||||
public Dictionary<string, SpecialRotationSetting>? Settings { get; set; }
|
||||
[JsonPropertyName("disabled_card_set_ids")]
|
||||
[Key("disabled_card_set_ids")]
|
||||
/// <summary>Prod wire key is <c>disable_card_set_ids</c> (no trailing 'd' on "disable").</summary>
|
||||
[JsonPropertyName("disable_card_set_ids")]
|
||||
[Key("disable_card_set_ids")]
|
||||
public List<int>? DisabledCardSets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,49 +3,74 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Pre-release window for an upcoming card set. Wire shape derived from 2026-05-23 prod capture:
|
||||
/// most numeric fields arrive as quoted strings (prod's PHP backend convention) — only the truly
|
||||
/// integer fields (card_master_id, statuses) are JSON numbers. Client parses with .ToInt()/.ToString()
|
||||
/// so either works on read, but matching prod is the right baseline.
|
||||
///
|
||||
/// Optional in /load/index — see Prerelease.Create for parser-side handling.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class PreReleaseInfo
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
[Key("id")]
|
||||
public int Id { get; set; }
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("start_time")]
|
||||
[Key("start_time")]
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
[JsonPropertyName("end_time")]
|
||||
[Key("end_time")]
|
||||
public DateTime EndTime { get; set; }
|
||||
|
||||
[JsonPropertyName("display_end_time")]
|
||||
[Key("display_end_time")]
|
||||
public DateTime DisplayEndTime { get; set; }
|
||||
|
||||
[JsonPropertyName("next_card_set_id")]
|
||||
[Key("next_card_set_id")]
|
||||
public int NextCardSetId { get; set; }
|
||||
public string NextCardSetId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("default_card_master_id")]
|
||||
[Key("default_card_master_id")]
|
||||
public int DefaultCardMasterId { get; set; }
|
||||
public string DefaultCardMasterId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("pre_release_card_master_id")]
|
||||
[Key("pre_release_card_master_id")]
|
||||
public int PreReleaseCardMasterId { get; set; }
|
||||
public string PreReleaseCardMasterId { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("free_match_start_time")]
|
||||
[Key("free_match_start_time")]
|
||||
public DateTime FreeMatchStartTime { get; set; }
|
||||
|
||||
[JsonPropertyName("card_master_id")]
|
||||
[Key("card_master_id")]
|
||||
public int CardMasterId { get; set; }
|
||||
|
||||
[JsonPropertyName("rotation_card_set_id_list")]
|
||||
[Key("rotation_card_set_id_list")]
|
||||
public List<int> RotationCardSets { get; set; } = new List<int>();
|
||||
public List<int> RotationCardSets { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Prod sends a dict of card_id (string) → card_id (string) — values mirror keys. The
|
||||
/// purpose is just to enumerate which base card ids count as reprinted in this window.
|
||||
/// </summary>
|
||||
[JsonPropertyName("reprinted_base_card_ids")]
|
||||
[Key("reprinted_base_card_ids")]
|
||||
public Dictionary<string, long> ReprintedCardIds { get; set; } = new Dictionary<string, long>();
|
||||
public Dictionary<string, string> ReprintedCardIds { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("latest_reprinted_base_card_ids")]
|
||||
[Key("latest_reprinted_base_card_ids")]
|
||||
public List<int> LatestReprintedCardIds { get; set; } = new List<int>();
|
||||
public List<int> LatestReprintedCardIds { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("pre_release_status")]
|
||||
[Key("pre_release_status")]
|
||||
public int PreReleaseStatus { get; set; }
|
||||
|
||||
[JsonPropertyName("is_pre_rotation_free_match_term")]
|
||||
[Key("is_pre_rotation_free_match_term")]
|
||||
public int IsPreRotationFreeMatchTerm { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
32
SVSim.EmulatedEntrypoint/Models/Dtos/Quest.cs
Normal file
32
SVSim.EmulatedEntrypoint/Models/Dtos/Quest.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// "quest" block on /mypage/index. Consumed by QuestOpenInfo.SetOpenInfo.
|
||||
/// Empty/closed-quest shape captured from prod 2026-05-23.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class Quest
|
||||
{
|
||||
[JsonPropertyName("is_open")]
|
||||
[Key("is_open")]
|
||||
public bool IsOpen { get; set; }
|
||||
|
||||
[JsonPropertyName("is_display_badge")]
|
||||
[Key("is_display_badge")]
|
||||
public bool IsDisplayBadge { get; set; }
|
||||
|
||||
[JsonPropertyName("is_daily_first_access")]
|
||||
[Key("is_daily_first_access")]
|
||||
public bool IsDailyFirstAccess { get; set; }
|
||||
|
||||
[JsonPropertyName("end_time")]
|
||||
[Key("end_time")]
|
||||
public string EndTime { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
[Key("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||
|
||||
[MessagePackObject]
|
||||
public class MyPageIndexRequest : BaseRequest
|
||||
{
|
||||
[JsonPropertyName("carrier")]
|
||||
[Key("carrier")]
|
||||
public string Carrier { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -4,14 +4,36 @@ using System.Text.Json.Serialization;
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Deck;
|
||||
|
||||
/// <summary>
|
||||
/// Shape consumed by `DeckGroupListData(jsonData, format)` for a single-format call 窶・/// the format-scoped decks land under `user_deck_list` (vs. the per-format keys used
|
||||
/// by /practice/deck_list with Format.All).
|
||||
/// Shape consumed by <c>DeckGroupListData(jsonData, format)</c>. Spec at
|
||||
/// <c>docs/api-spec/endpoints/post-login/deck-info.md</c> only enumerates <c>maintenance_card_list</c>
|
||||
/// and <c>user_deck_list</c> explicitly (with <c>[k: string]: unknown</c> for the rest); the 2026-05-23
|
||||
/// prod capture filled in the gap — <c>default_deck_list</c>, <c>user_leader_skin_setting_list</c>,
|
||||
/// and <c>trial_deck_list</c> are all present and sourced from globals.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class DeckListResponse
|
||||
{
|
||||
[JsonPropertyName("maintenance_card_list")]
|
||||
[Key("maintenance_card_list")] public List<long> MaintenanceCardList { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("user_deck_list")]
|
||||
[Key("user_deck_list")] public List<UserDeck>? UserDeckList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Global starter decks, keyed by deck_no as string (prod ids 91-98 — one per class).
|
||||
/// </summary>
|
||||
[JsonPropertyName("default_deck_list")]
|
||||
[Key("default_deck_list")] public Dictionary<string, DefaultDeck> DefaultDeckList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Default leader skin per class, keyed by class_id as string.
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_leader_skin_setting_list")]
|
||||
[Key("user_leader_skin_setting_list")] public Dictionary<string, DefaultLeaderSkinSetting> UserLeaderSkinSettingList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Trial / tutorial-specific decks. Empty in the 2026-05-23 prod capture; entry shape TBD.
|
||||
/// </summary>
|
||||
[JsonPropertyName("trial_deck_list")]
|
||||
[Key("trial_deck_list")] public List<UserDeck> TrialDeckList { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MessagePack;
|
||||
using SVSim.Database.Enums;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
|
||||
@@ -24,15 +25,22 @@ public class IndexResponse
|
||||
[JsonPropertyName("room_recovery_status")]
|
||||
[Key("room_recovery_status")]
|
||||
public int RoomRecoveryStatus { get; set; }
|
||||
/// <summary>
|
||||
/// Prod emits this as bool (per the 2026-05-23 capture); the spec leaves it as a TODO
|
||||
/// (load-index.md line 296-297). We send bool to match prod; client's `.ToBoolean()`
|
||||
/// path handles either shape, but matching prod avoids the int-vs-bool drift noted in
|
||||
/// the seed-data-strategy crash audit.
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_battle_pass_period")]
|
||||
[Key("is_battle_pass_period")]
|
||||
public int IsBattlePassPeriod { get; set; }
|
||||
public bool IsBattlePassPeriod { get; set; }
|
||||
[JsonPropertyName("card_set_id_for_resource_dl_view")]
|
||||
[Key("card_set_id_for_resource_dl_view")]
|
||||
public int CardSetIdForResourceDlView { get; set; }
|
||||
// Serialized as wire deck_format via FormatJsonConverter (registered in Program.cs).
|
||||
[JsonPropertyName("deck_format")]
|
||||
[Key("deck_format")]
|
||||
public int DeckFormat { get; set; } = 1;
|
||||
public Format DeckFormat { get; set; } = Format.Rotation;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -123,9 +131,15 @@ public class IndexResponse
|
||||
[Key("user_rank_match_list")]
|
||||
public List<UserRankedMatches> UserRankedMatches { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Spec: optional. Shape is {normal?, total?, campaign?[]} per common/types.ts.md DailyLoginBonus.
|
||||
/// Until we have an active login-bonus campaign to surface in spec shape, omit. The skeleton
|
||||
/// rows in DailyLoginBonuses table (prod sent {"1":[], "3":[], "4":[]}) preserve the capture
|
||||
/// for archive but don't make it into the wire response.
|
||||
/// </summary>
|
||||
[JsonPropertyName("daily_login_bonus")]
|
||||
[Key("daily_login_bonus")]
|
||||
public DailyLoginBonus DailyLoginBonus { get; set; } = new();
|
||||
public DailyLoginBonus? DailyLoginBonus { get; set; }
|
||||
|
||||
[JsonPropertyName("challenge_config")]
|
||||
[Key("challenge_config")]
|
||||
@@ -213,7 +227,7 @@ public class IndexResponse
|
||||
|
||||
[JsonPropertyName("avatar_info")]
|
||||
[Key("avatar_info")]
|
||||
public MyRotationInfo? AvatarRotationInfo { get; set; }
|
||||
public AvatarInfo? AvatarRotationInfo { get; set; }
|
||||
|
||||
[JsonPropertyName("feature_maintenance_list")]
|
||||
[Key("feature_maintenance_list")]
|
||||
@@ -223,6 +237,10 @@ public class IndexResponse
|
||||
[Key("special_crystal_info")]
|
||||
public List<SpecialCrystalInfo> SpecialCrystalInfos { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Spec: optional, Record<string, BattlePassLevelInfo> keyed by level-as-string
|
||||
/// (load-index.md:228). Omit (null) when no Battle Pass is active.
|
||||
/// </summary>
|
||||
[JsonPropertyName("battle_pass_level_info")]
|
||||
[Key("battle_pass_level_info")]
|
||||
public Dictionary<string, BattlePassLevel>? BattlePassLevelInfo { get; set; }
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
using MessagePack;
|
||||
using SVSim.Database.Enums;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// /mypage/index ("home screen refresh") response payload.
|
||||
///
|
||||
/// Required fields per the minimum-viable section of
|
||||
/// docs/api-spec/endpoints/post-login/mypage-index.md and corroborated by
|
||||
/// MyPageTask.cs direct-index accesses (jsonData["…"] without TryGetValue).
|
||||
/// Optional fields are nullable and omitted by the global WhenWritingNull
|
||||
/// policy — the client uses TryGetValue / GetValueOrDefault for those.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class MyPageIndexResponse
|
||||
{
|
||||
// ── User identity / counts ─────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Full UserInfo block. Client only reads .name here (MyPageTask.cs:39) but
|
||||
/// prod emits the full structure, so we do too.
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_info")]
|
||||
[Key("user_info")]
|
||||
public UserInfo UserInfo { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("unreceived_mission_reward_count")]
|
||||
[Key("unreceived_mission_reward_count")]
|
||||
public int UnreceivedMissionRewardCount { get; set; }
|
||||
|
||||
[JsonPropertyName("receive_friend_apply_count")]
|
||||
[Key("receive_friend_apply_count")]
|
||||
public int ReceiveFriendApplyCount { get; set; }
|
||||
|
||||
[JsonPropertyName("unread_present_count")]
|
||||
[Key("unread_present_count")]
|
||||
public int UnreadPresentCount { get; set; }
|
||||
|
||||
[JsonPropertyName("friend_battle_invite_count")]
|
||||
[Key("friend_battle_invite_count")]
|
||||
public int FriendBattleInviteCount { get; set; }
|
||||
|
||||
// ── Guild ──────────────────────────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("guild_notification")]
|
||||
[Key("guild_notification")]
|
||||
public GuildNotification GuildNotification { get; set; } = new();
|
||||
|
||||
// ── Announcements ──────────────────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("last_announce_id")]
|
||||
[Key("last_announce_id")]
|
||||
public int LastAnnounceId { get; set; }
|
||||
|
||||
/// <summary>ISO datetime. Parse is wrapped in try/catch on the client.</summary>
|
||||
[JsonPropertyName("last_announce_update_time")]
|
||||
[Key("last_announce_update_time")]
|
||||
public string LastAnnounceUpdateTime { get; set; } = string.Empty;
|
||||
|
||||
// ── Maintenance ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Same shape as /load/index. Empty list in the 2026-05-23 capture.</summary>
|
||||
[JsonPropertyName("feature_maintenance_list")]
|
||||
[Key("feature_maintenance_list")]
|
||||
public List<FeatureMaintenance> FeatureMaintenanceList { get; set; } = new();
|
||||
|
||||
// ── Arena / Colosseum ──────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Client unconditionally constructs ArenaData(arena_info) which reads [0],
|
||||
/// so this MUST be a non-empty list. See LoadController BuildArenaInfosAsync
|
||||
/// — we mirror that, returning null (omitted on wire) when no Take Two
|
||||
/// season is seeded, in which case the client's Keys.Contains guard at
|
||||
/// LoadDetail.cs:261 handles it. For mypage there is no equivalent guard;
|
||||
/// the client always reads it. Until that's reconciled we send a minimal
|
||||
/// stub on the controller side.
|
||||
/// </summary>
|
||||
[JsonPropertyName("arena_info")]
|
||||
[Key("arena_info")]
|
||||
public List<ArenaInfo> ArenaInfo { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("is_arena_challenge_period")]
|
||||
[Key("is_arena_challenge_period")]
|
||||
public bool IsArenaChallengePeriod { get; set; }
|
||||
|
||||
[JsonPropertyName("is_available_colosseum_free_entry")]
|
||||
[Key("is_available_colosseum_free_entry")]
|
||||
public bool IsAvailableColosseumFreeEntry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required — ColosseumEntryInfoTask.SetColosseumInfo indexes this key
|
||||
/// directly (Wizard/ColosseumEntryInfoTask.cs:102) and reads
|
||||
/// is_colosseum_period without a guard.
|
||||
/// </summary>
|
||||
[JsonPropertyName("colosseum_info")]
|
||||
[Key("colosseum_info")]
|
||||
public ColosseumInfo ColosseumInfo { get; set; } = new();
|
||||
|
||||
// ── Convention / offline event ─────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("convention")]
|
||||
[Key("convention")]
|
||||
public Convention Convention { get; set; } = new();
|
||||
|
||||
// ── Battle / room recovery (optional) ─────────────────────────────────
|
||||
|
||||
[JsonPropertyName("unfinished_battle_exists")]
|
||||
[Key("unfinished_battle_exists")]
|
||||
public bool? UnfinishedBattleExists { get; set; }
|
||||
|
||||
[JsonPropertyName("battle_finish_wait_time")]
|
||||
[Key("battle_finish_wait_time")]
|
||||
public int? BattleFinishWaitTime { get; set; }
|
||||
|
||||
[JsonPropertyName("is_joined_room")]
|
||||
[Key("is_joined_room")]
|
||||
public bool? IsJoinedRoom { get; set; }
|
||||
|
||||
// ── Login bonus (optional) ─────────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("can_give_daily_login_bonus")]
|
||||
[Key("can_give_daily_login_bonus")]
|
||||
public bool? CanGiveDailyLoginBonus { get; set; }
|
||||
|
||||
// ── User config (settings echo) ────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("user_config")]
|
||||
[Key("user_config")]
|
||||
public UserConfig UserConfig { get; set; } = new();
|
||||
|
||||
// ── Quest progress ─────────────────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("quest")]
|
||||
[Key("quest")]
|
||||
public Quest Quest { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Required — QuestOpenInfo.SetOpenInfo unconditionally calls .ToBoolean()
|
||||
/// on this root-level field (Wizard/QuestOpenInfo.cs:32). Omitting it would
|
||||
/// surface as a parse crash, not a defaulted value.
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_hidden_boss_appeared")]
|
||||
[Key("is_hidden_boss_appeared")]
|
||||
public bool IsHiddenBossAppeared { get; set; }
|
||||
|
||||
// ── Master Points season window ────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("master_point_ranking_period")]
|
||||
[Key("master_point_ranking_period")]
|
||||
public MasterPointRankingPeriod MasterPointRankingPeriod { get; set; } = new();
|
||||
|
||||
// ── Pre-release card preview ───────────────────────────────────────────
|
||||
|
||||
/// <summary>Number cast to Prerelease.eStatus on the client.</summary>
|
||||
[JsonPropertyName("pre_release_status")]
|
||||
[Key("pre_release_status")]
|
||||
public int PreReleaseStatus { get; set; }
|
||||
|
||||
// ── MyPage background ──────────────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("user_mypage_info")]
|
||||
[Key("user_mypage_info")]
|
||||
public UserMyPageInfo UserMyPageInfo { get; set; } = new();
|
||||
|
||||
// ── Basic puzzle badge ─────────────────────────────────────────────────
|
||||
|
||||
[JsonPropertyName("basic_puzzle")]
|
||||
[Key("basic_puzzle")]
|
||||
public BasicPuzzle BasicPuzzle { get; set; } = new();
|
||||
|
||||
// ── Battle Pass period flag ────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Parsed by Data.ParseIsBattlePassPeriod. Same field as on /load/index
|
||||
/// (prod emits bool there too).
|
||||
/// </summary>
|
||||
[JsonPropertyName("is_battle_pass_period")]
|
||||
[Key("is_battle_pass_period")]
|
||||
public bool IsBattlePassPeriod { get; set; }
|
||||
|
||||
// ── Special crystal info ───────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Sibling under data, same shape as /load/index. Empty in the prod capture.
|
||||
/// </summary>
|
||||
[JsonPropertyName("special_crystal_info")]
|
||||
[Key("special_crystal_info")]
|
||||
public List<SpecialCrystalInfo> SpecialCrystalInfo { get; set; } = new();
|
||||
|
||||
// ── Notification setters that index root-of-data directly ──────────────
|
||||
|
||||
/// <summary>
|
||||
/// Required — ShopNotification.SetShopNotification indexes the four nested
|
||||
/// keys (card_pack, build_deck, sleeve, leader_skin) without TryGetValue
|
||||
/// (Wizard/ShopNotification.cs:33-37). The inner ShopAppealInfo ctor early-
|
||||
/// returns on empty, so default-constructed values are safe.
|
||||
/// </summary>
|
||||
[JsonPropertyName("shop_notification")]
|
||||
[Key("shop_notification")]
|
||||
public ShopNotification ShopNotification { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Required — StoryNotification.SetStoryNotification indexes this key
|
||||
/// directly (Wizard/StoryNotification.cs:22) before applying GetValueOrDefault
|
||||
/// to its sub-fields.
|
||||
/// </summary>
|
||||
[JsonPropertyName("story_notification")]
|
||||
[Key("story_notification")]
|
||||
public StoryNotification StoryNotification { get; set; } = new();
|
||||
|
||||
// ── Optional UI surface area ───────────────────────────────────────────
|
||||
|
||||
/// <summary>Updated item counts. Refreshes Data.Load.data._userItemDict when present.</summary>
|
||||
[JsonPropertyName("user_item_list")]
|
||||
[Key("user_item_list")]
|
||||
public List<UserItem>? UserItemList { get; set; }
|
||||
|
||||
[JsonPropertyName("gathering_info")]
|
||||
[Key("gathering_info")]
|
||||
public GatheringInfo? GatheringInfo { get; set; }
|
||||
}
|
||||
51
SVSim.EmulatedEntrypoint/Models/Dtos/ShopNotification.cs
Normal file
51
SVSim.EmulatedEntrypoint/Models/Dtos/ShopNotification.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// shop_notification on /mypage/index, consumed by
|
||||
/// ShopNotification.SetShopNotification (Wizard/ShopNotification.cs:30). All four
|
||||
/// sub-keys are directly indexed; each is passed to ShopAppealInfo, which early-
|
||||
/// returns when `data.Count == 0`. We mirror prod's heterogeneous shape — three
|
||||
/// empty arrays and one object — so that the wire matches and the client's
|
||||
/// length-check fires on the empty cases.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class ShopNotification
|
||||
{
|
||||
[JsonPropertyName("card_pack")]
|
||||
[Key("card_pack")]
|
||||
public ShopCardPackAppeal CardPack { get; set; } = new();
|
||||
|
||||
/// <summary>Prod 2026-05-23: <c>[]</c>. Client treats Count==0 as "no notification".</summary>
|
||||
[JsonPropertyName("build_deck")]
|
||||
[Key("build_deck")]
|
||||
public List<object> BuildDeck { get; set; } = new();
|
||||
|
||||
/// <summary>Prod 2026-05-23: <c>[]</c>.</summary>
|
||||
[JsonPropertyName("sleeve")]
|
||||
[Key("sleeve")]
|
||||
public List<object> Sleeve { get; set; } = new();
|
||||
|
||||
/// <summary>Prod 2026-05-23: <c>[]</c>.</summary>
|
||||
[JsonPropertyName("leader_skin")]
|
||||
[Key("leader_skin")]
|
||||
public List<object> LeaderSkin { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// card_pack sub-object — drives the free-gacha campaign badge. Both fields are
|
||||
/// TryGetValue on the client; emitting both as false matches prod's idle shape.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class ShopCardPackAppeal
|
||||
{
|
||||
[JsonPropertyName("is_open_free_gacha_campaign")]
|
||||
[Key("is_open_free_gacha_campaign")]
|
||||
public bool IsOpenFreeGachaCampaign { get; set; }
|
||||
|
||||
[JsonPropertyName("can_free_gacha")]
|
||||
[Key("can_free_gacha")]
|
||||
public bool CanFreeGacha { get; set; }
|
||||
}
|
||||
21
SVSim.EmulatedEntrypoint/Models/Dtos/StoryNotification.cs
Normal file
21
SVSim.EmulatedEntrypoint/Models/Dtos/StoryNotification.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// story_notification on /mypage/index, consumed by
|
||||
/// StoryNotification.SetStoryNotification (Wizard/StoryNotification.cs:20). The
|
||||
/// outer key is directly indexed; the inner fields are TryGetValue-defaulted.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class StoryNotification
|
||||
{
|
||||
[JsonPropertyName("is_display_ribbon")]
|
||||
[Key("is_display_ribbon")]
|
||||
public bool IsDisplayRibbon { get; set; }
|
||||
|
||||
[JsonPropertyName("is_display_badge")]
|
||||
[Key("is_display_badge")]
|
||||
public bool IsDisplayBadge { get; set; }
|
||||
}
|
||||
37
SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPageInfo.cs
Normal file
37
SVSim.EmulatedEntrypoint/Models/Dtos/UserMyPageInfo.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// user_mypage_info — wrapper around the active home-screen background
|
||||
/// configuration. Client constructs MyPageBGInfo(user_mypage_setting) at
|
||||
/// MyPageTask.cs:176.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class UserMyPageInfo
|
||||
{
|
||||
[JsonPropertyName("user_mypage_setting")]
|
||||
[Key("user_mypage_setting")]
|
||||
public MyPageBgSetting UserMyPageSetting { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Active mypage background selection. Shape from prod 2026-05-23.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class MyPageBgSetting
|
||||
{
|
||||
[JsonPropertyName("mypage_id")]
|
||||
[Key("mypage_id")]
|
||||
public int MyPageId { get; set; }
|
||||
|
||||
/// <summary>0 = single selection (mypage_id), 1+ = random rotation across mypage_id_list.</summary>
|
||||
[JsonPropertyName("select_type")]
|
||||
[Key("select_type")]
|
||||
public int SelectType { get; set; }
|
||||
|
||||
[JsonPropertyName("mypage_id_list")]
|
||||
[Key("mypage_id_list")]
|
||||
public List<int> MyPageIdList { get; set; } = new();
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
using MessagePack;
|
||||
using System.Text.Json.Serialization;
|
||||
using SVSim.Database.Enums;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
[MessagePackObject]
|
||||
public class UserRankInfo
|
||||
{
|
||||
// Serialized as wire deck_format via FormatJsonConverter (registered globally in
|
||||
// Program.cs). Storing as Format makes wrong-int-scope bugs (sending internal enum
|
||||
// ints instead of wire codes) a compile error.
|
||||
[JsonPropertyName("deck_format")]
|
||||
[Key("deck_format")]
|
||||
public int DeckFormat { get; set; }
|
||||
public Format DeckFormat { get; set; }
|
||||
[JsonPropertyName("rank")]
|
||||
[Key("rank")]
|
||||
public int Rank { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user