More features
This commit is contained in:
@@ -6,5 +6,5 @@ namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
public class EmblemIdentifier
|
||||
{
|
||||
[Key("emblem_id")]
|
||||
public int EmblemId { get; set; }
|
||||
public long EmblemId { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
using MessagePack;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||
|
||||
[MessagePackObject]
|
||||
public class IndexRequest : BaseRequest
|
||||
{
|
||||
[Key("carrier")]
|
||||
public string Carrier { get; set; }
|
||||
|
||||
[Key("card_master_hash")]
|
||||
public string CardMasterHash { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,83 @@ using MessagePack;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Wire-shape mirrors production's <c>/check/game_start</c> response. Several fields here are
|
||||
/// NOT read by <c>Cute/GameStartCheckTask.Parse</c> (<c>now_viewer_id</c>, <c>now_name</c>,
|
||||
/// <c>now_rank</c> — those are consumed by sibling tasks); they're included because prod sends
|
||||
/// them and the boot worked when we matched prod exactly. Removing them is a regression risk
|
||||
/// even though the parse-time decompile says they're unused.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class GameStartResponse
|
||||
{
|
||||
/// <summary>The signed-in viewer's internal id. Prod always sends.</summary>
|
||||
[Key("now_viewer_id")]
|
||||
public long NowViewerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user has set a data-transfer password. Prod sends a non-null bool;
|
||||
/// <c>GameStartCheckTask.Parse</c> gates the read with <c>Keys.Contains</c>.
|
||||
/// </summary>
|
||||
[Key("is_set_transition_password")]
|
||||
public bool IsSetTransitionPassword { get; set; }
|
||||
|
||||
/// <summary>Viewer display name. Not read by GameStartCheckTask but sent by prod.</summary>
|
||||
[Key("now_name")]
|
||||
public string NowName { get; set; }
|
||||
public string NowName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Per-format rank-name map keyed by deck-format id ("1", "2", "4" observed in prod).
|
||||
/// Stub for now until rank state is persisted; pinned to RankName_010 / RankName_017
|
||||
/// (matches prod's shape).
|
||||
/// </summary>
|
||||
[Key("now_rank")]
|
||||
public Dictionary<string, string> NowRank { get; set; }
|
||||
public Dictionary<string, string> NowRank { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Tutorial progress — **sent as a string on the wire** ("100" = tutorial complete).
|
||||
/// <c>GameStartCheckTask.Parse</c> calls <c>.ToInt()</c> so LitJson coerces.
|
||||
/// </summary>
|
||||
[Key("now_tutorial_step")]
|
||||
public string NowTutorialStep { get; set; }
|
||||
public string NowTutorialStep { get; set; } = "100";
|
||||
|
||||
/// <summary>
|
||||
/// Linked social accounts. Per-entry shape in <see cref="TransitionAccountData"/>.
|
||||
/// </summary>
|
||||
[Key("transition_account_data")]
|
||||
public List<TransitionAccountData> TransitionAccountData { get; set; }
|
||||
public List<TransitionAccountData> TransitionAccountData { get; set; } = new();
|
||||
|
||||
// INTENTIONALLY OMITTED: `rewrite_viewer_id` and `account_delete_reservation_status`.
|
||||
// Both are presence-checked by the client via `Keys.Contains(...)` + `.ToInt()` with no
|
||||
// null guard. MessagePack-CSharp writes [Key] properties unconditionally (null → Nil),
|
||||
// and the System.Text.Json `WhenWritingNull` ignore only affects the plain-JSON path.
|
||||
// So including these as nullable properties is a guaranteed NRE on the encrypted client
|
||||
// path. We don't need them — the client tolerates their absence — so don't declare them.
|
||||
// Re-add only if we have a real value to send.
|
||||
|
||||
// --- Agreement / consent state (all required) ---
|
||||
|
||||
/// <summary><c>PlayerStaticData.AgreementState</c> enum.</summary>
|
||||
[Key("tos_state")]
|
||||
public int TosState { get; set; }
|
||||
[Key("tos_id")]
|
||||
public int TosId { get; set; }
|
||||
|
||||
/// <summary><c>PlayerStaticData.AgreementState</c> enum.</summary>
|
||||
[Key("policy_state")]
|
||||
public int PolicyState { get; set; }
|
||||
[Key("policy_id")]
|
||||
public int PolicyId { get; set; }
|
||||
[Key("kor_authority_id")]
|
||||
public int KorAuthorityId { get; set; }
|
||||
|
||||
/// <summary><c>PlayerStaticData.AgreementState</c> enum.</summary>
|
||||
[Key("kor_authority_state")]
|
||||
public int KorAuthorityState { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Current Terms of Service document id.</summary>
|
||||
[Key("tos_id")]
|
||||
public int TosId { get; set; }
|
||||
|
||||
/// <summary>Current Privacy Policy document id.</summary>
|
||||
[Key("policy_id")]
|
||||
public int PolicyId { get; set; }
|
||||
|
||||
/// <summary>Current Korean authority consent document id.</summary>
|
||||
[Key("kor_authority_id")]
|
||||
public int KorAuthorityId { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ public class IndexResponse
|
||||
{
|
||||
#region Primitive Returns
|
||||
|
||||
[Key("ts_card_rotation")]
|
||||
public string TsCardRotation { get; set; } = string.Empty;
|
||||
[Key("is_beginner_mission")]
|
||||
public int IsBeginnerMission { get; set; }
|
||||
[Key("spot_point")]
|
||||
public int SpotPoint { get; set; }
|
||||
[Key("is_available_colosseum_free_entry")]
|
||||
@@ -23,29 +19,22 @@ public class IndexResponse
|
||||
[Key("room_recovery_status")]
|
||||
public int RoomRecoveryStatus { get; set; }
|
||||
[Key("is_battle_pass_period")]
|
||||
public bool IsBattlePassPeriod { get; set; }
|
||||
public int IsBattlePassPeriod { get; set; }
|
||||
[Key("card_set_id_for_resource_dl_view")]
|
||||
public int CardSetIdForResourceDlView { get; set; }
|
||||
[Key("deck_format")]
|
||||
public int DeckFormat { get; set; } = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic User Data
|
||||
|
||||
/// <summary>
|
||||
/// The user's tutorial progress state.
|
||||
/// </summary>
|
||||
[Key("user_tutorial")]
|
||||
public UserTutorial UserTutorial { get; set; } = new UserTutorial();
|
||||
|
||||
/// <summary>
|
||||
/// Basic information about the user.
|
||||
/// </summary>
|
||||
|
||||
[Key("user_info")]
|
||||
public UserInfo UserInfo { get; set; } = new UserInfo();
|
||||
|
||||
/// <summary>
|
||||
/// The in-game currency information for this user.
|
||||
/// </summary>
|
||||
[Key("user_crystal_count")]
|
||||
public UserCurrency UserCurrency { get; set; } = new UserCurrency();
|
||||
|
||||
@@ -53,234 +42,163 @@ public class IndexResponse
|
||||
|
||||
#region Inventory Data
|
||||
|
||||
/// <summary>
|
||||
/// Items that the user has and how many of each.
|
||||
/// </summary>
|
||||
[Key("user_item_list")]
|
||||
public List<UserItem> UserItems { get; set; } = new List<UserItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Decks for the rotation format.
|
||||
/// </summary>
|
||||
[Key("user_item_list")]
|
||||
public List<UserItem> UserItems { get; set; } = new();
|
||||
|
||||
[Key("user_deck_rotation")]
|
||||
public UserFormatDeckInfo UserRotationDecks { get; set; } = new UserFormatDeckInfo();
|
||||
public UserFormatDeckInfo UserRotationDecks { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Decks for the unlimited format.
|
||||
/// </summary>
|
||||
[Key("user_deck_unlimited")]
|
||||
public UserFormatDeckInfo UserUnlimitedDecks { get; set; } = new UserFormatDeckInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Decks for the unlimited format.
|
||||
/// </summary>
|
||||
public UserFormatDeckInfo UserUnlimitedDecks { get; set; } = new();
|
||||
|
||||
[Key("user_deck_my_rotation")]
|
||||
public UserFormatDeckInfo UserMyRotationDecks { get; set; } = new UserFormatDeckInfo();
|
||||
public UserFormatDeckInfo UserMyRotationDecks { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of cards and how many of each this user has.
|
||||
/// </summary>
|
||||
[Key("user_card_list")]
|
||||
public List<UserCard> UserCards { get; set; } = new List<UserCard>();
|
||||
public List<UserCard> UserCards { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The classes a user has and their stats.
|
||||
/// </summary>
|
||||
[Key("user_class_list")]
|
||||
public List<UserClass> UserClasses { get; set; } = new List<UserClass>();
|
||||
public List<UserClass> UserClasses { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of SleeveId to a <see cref="SleeveIdentifier"/> object.
|
||||
/// Wire is an array; parser iterates by index (LoadDetail.cs:358-360).
|
||||
/// </summary>
|
||||
[Key("user_sleeve_list")]
|
||||
public Dictionary<string, SleeveIdentifier> Sleeves { get; set; } = new Dictionary<string, SleeveIdentifier>();
|
||||
public List<SleeveIdentifier> Sleeves { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The emblems available to this user.
|
||||
/// </summary>
|
||||
[Key("user_emblem_list")]
|
||||
public List<EmblemIdentifier> UserEmblems { get; set; } = new List<EmblemIdentifier>();
|
||||
public List<EmblemIdentifier> UserEmblems { get; set; } = new();
|
||||
|
||||
[Key("user_degree_list")]
|
||||
public List<DegreeIdentifier> UserDegrees { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The degrees available to this user.
|
||||
/// </summary>
|
||||
[Key("degree_id")]
|
||||
public List<DegreeIdentifier> UserDegrees { get; set; } = new List<DegreeIdentifier>();
|
||||
|
||||
/// <summary>
|
||||
/// Leader skins available to the leader.
|
||||
/// Wire is an array; parser iterates by index (LoadDetail.cs:348-356).
|
||||
/// </summary>
|
||||
[Key("user_leader_skin_list")]
|
||||
public Dictionary<string, UserLeaderSkin> LeaderSkins { get; set; } = new Dictionary<string, UserLeaderSkin>();
|
||||
|
||||
public List<UserLeaderSkin> LeaderSkins { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Backgrounds for 'My Page' the user has collected.
|
||||
/// Wire is string[]; parser calls .ToString() on each element (LoadDetail.cs:387-392).
|
||||
/// </summary>
|
||||
[Key("user_mypage_list")]
|
||||
public List<int> MyPageBackgrounds { get; set; } = new List<int>();
|
||||
public List<string> MyPageBackgrounds { get; set; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Advanced Player Data
|
||||
|
||||
/// <summary>
|
||||
/// Maps a deck format (as a string) to info about ranked for that format.
|
||||
/// Wire is an array of 5 entries; parser uses deck_format as discriminator
|
||||
/// (LoadDetail.cs:527-538).
|
||||
/// </summary>
|
||||
[Key("user_rank")]
|
||||
public Dictionary<string, UserRankInfo> UserRankInfo { get; set; } = new Dictionary<string, UserRankInfo>();
|
||||
public List<UserRankInfo> UserRankInfo { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The number of ranked matches for each class the user has played.
|
||||
/// </summary>
|
||||
[Key("user_rank_match_list")]
|
||||
public List<UserRankedMatches> UserRankedMatches { get; set; } = new List<UserRankedMatches>();
|
||||
public List<UserRankedMatches> UserRankedMatches { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The daily login bonuses currently going on, including if the player should receive the next reward for any.
|
||||
/// </summary>
|
||||
[Key("daily_login_bonus")]
|
||||
public DailyLoginBonus DailyLoginBonus { get; set; } = new DailyLoginBonus();
|
||||
public DailyLoginBonus DailyLoginBonus { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// User configuration for the arena.
|
||||
/// </summary>
|
||||
[Key("challenge_config")]
|
||||
public ArenaConfig ArenaConfig { get; set; } = new ArenaConfig();
|
||||
public ArenaConfig ArenaConfig { get; set; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Global Data
|
||||
|
||||
/// <summary>
|
||||
/// Cards that have had their red ether values overriden.
|
||||
/// </summary>
|
||||
[Key("red_ether_overwrite_list")]
|
||||
public List<RedEtherOverride> RedEtherOverrides { get; set; } = new List<RedEtherOverride>();
|
||||
public List<RedEtherOverride> RedEtherOverrides { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Cards that are currently undergoing maintenance.
|
||||
/// Wire is a flat number[]; parser passes it straight to SetMaintenanceCardIds
|
||||
/// (LoadDetail.cs:165).
|
||||
/// </summary>
|
||||
[Key("maintenance_card_list")]
|
||||
public List<CardIdentifier> MaintenanceCards { get; set; } = new List<CardIdentifier>();
|
||||
|
||||
/// <summary>
|
||||
/// The arena formats currently available.
|
||||
/// </summary>
|
||||
public List<long> MaintenanceCards { get; set; } = new();
|
||||
|
||||
[Key("arena_info")]
|
||||
public List<ArenaInfo> ArenaInfos { get; set; } = new List<ArenaInfo>();
|
||||
public List<ArenaInfo> ArenaInfos { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of rank id to information about that rank.
|
||||
/// Wire is an array; client uses POSITIONAL logic (index >= 24 = master ranks,
|
||||
/// LoadDetail.cs:417-422). Order must match repository's ordering.
|
||||
/// </summary>
|
||||
[Key("rank_info")]
|
||||
public Dictionary<string, RankInfo> RankInfo { get; set; } = new Dictionary<string, RankInfo>();
|
||||
public List<RankInfo> RankInfo { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary mapping a class level to information about that level.
|
||||
/// Wire is an array; parser iterates by index (LoadDetail.cs:425-434).
|
||||
/// </summary>
|
||||
[Key("class_exp")]
|
||||
public Dictionary<string, ClassExp> ClassExp { get; set; } = new Dictionary<string, ClassExp>();
|
||||
public List<ClassExp> ClassExp { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Card ids that should not show up on loading screen tips.
|
||||
/// </summary>
|
||||
[Key("loading_exclusion_card_list")]
|
||||
public List<long> LoadingTipCardExclusions { get; set; } = new List<long>();
|
||||
public List<long> LoadingTipCardExclusions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The default settings for every user.
|
||||
/// </summary>
|
||||
[Key("default_setting")]
|
||||
public DefaultSettings DefaultSettings { get; set; } = new DefaultSettings();
|
||||
public DefaultSettings DefaultSettings { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Any cards that are restricted in unlimited, and the maximum count that can be run of the card.
|
||||
/// </summary>
|
||||
[Key("unlimited_restricted_base_card_id_list")]
|
||||
public Dictionary<string, int> UnlimitedBanList { get; set; } = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> UnlimitedBanList { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Sets currently available in rotation.
|
||||
/// Client unconditionally accesses [1] and [Count-1] (LoadDetail.cs:184) — list MUST
|
||||
/// have at least 2 entries or the client crashes.
|
||||
/// </summary>
|
||||
[Key("rotation_card_set_id_list")]
|
||||
public List<CardSetIdentifier> RotationSets { get; set; } = new List<CardSetIdentifier>();
|
||||
public List<CardSetIdentifier> RotationSets { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Allows cards out of your 'My Rotation' to still be used. TODO investigate
|
||||
/// Wire is a flat number[]; parser iterates and reads .ToInt() (LoadDetail.cs:463-468).
|
||||
/// </summary>
|
||||
[Key("reprinted_base_card_ids")]
|
||||
public Dictionary<string, long> ReprintedCards { get; set; } = new Dictionary<string, long>();
|
||||
public List<long> ReprintedCards { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Something to do with destroying cards. TODO investigate
|
||||
/// </summary>
|
||||
[Key("spot_cards")]
|
||||
public Dictionary<string, int> SpotCards { get; set; } = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> SpotCards { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Info about the next set to be released? TODO investigate
|
||||
/// </summary>
|
||||
[Key("pre_release_info")]
|
||||
public PreReleaseInfo? PreReleaseInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information for the 'My Rotation' mode.
|
||||
/// </summary>
|
||||
|
||||
[Key("my_rotation_info")]
|
||||
public MyRotationInfo? MyRotationInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about some avatar mode? TODO investigate
|
||||
/// </summary>
|
||||
|
||||
[Key("avatar_info")]
|
||||
public MyRotationInfo? AvatarRotationInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of features that are undergoing maintenance.
|
||||
/// </summary>
|
||||
|
||||
[Key("feature_maintenance_list")]
|
||||
public List<FeatureMaintenance> FeatureMaintenances { get; set; } = new List<FeatureMaintenance>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Special deals on crystal purchases.
|
||||
/// </summary>
|
||||
public List<FeatureMaintenance> FeatureMaintenances { get; set; } = new();
|
||||
|
||||
[Key("special_crystal_info")]
|
||||
public List<SpecialCrystalInfo> SpecialCrystalInfos { get; set; } = new List<SpecialCrystalInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Current battle pass levels and required points for each.
|
||||
/// </summary>
|
||||
public List<SpecialCrystalInfo> SpecialCrystalInfos { get; set; } = new();
|
||||
|
||||
[Key("battle_pass_level_info")]
|
||||
public Dictionary<string, BattlePassLevel>? BattlePassLevelInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Battlefields that can currently be picked.
|
||||
/// Wire is string[]; parser calls .ToString() on each element (LoadDetail.cs:493-499).
|
||||
/// </summary>
|
||||
[Key("open_battle_field_id_list")]
|
||||
public Dictionary<string, int> OpenBattlefieldIds { get; set; } = new Dictionary<string, int>();
|
||||
public List<string> OpenBattlefieldIds { get; set; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Misc Data
|
||||
|
||||
/// <summary>
|
||||
/// What loot box features are disabled for this user.
|
||||
/// </summary>
|
||||
[Key("loot_box_regulation")]
|
||||
public LootBoxRegulations LootBoxRegulations { get; set; } = new LootBoxRegulations();
|
||||
public LootBoxRegulations LootBoxRegulations { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Something about whether the user has an invite notification.
|
||||
/// </summary>
|
||||
[Key("gathering_info")]
|
||||
public GatheringInfo GatheringInfo { get; set; } = new GatheringInfo();
|
||||
public GatheringInfo GatheringInfo { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// User configuration.
|
||||
/// Spec is unclear whether this is returned at /load/index or only at /config/* endpoints
|
||||
/// (load-index.md line 390). Pending live-capture confirmation; harmless extra.
|
||||
/// </summary>
|
||||
[Key("user_config")]
|
||||
public UserConfig UserConfig { get; set; } = new UserConfig();
|
||||
public UserConfig UserConfig { get; set; } = new();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
|
||||
[MessagePackObject]
|
||||
public class SpecialTitleCheckResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Numeric string. "0"/"1" are the built-in default title screens; any other value
|
||||
/// is treated as an asset-bundle id. When omitted, the client defaults to "0".
|
||||
/// </summary>
|
||||
[Key("title_image_id")]
|
||||
public int TitleImageId { get; set; }
|
||||
[Key("title_sound_id")]
|
||||
public int TitleSoundId { get; set; }
|
||||
}
|
||||
public string? TitleImageId { get; set; }
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@ public class SleeveIdentifier
|
||||
/// The id of the sleeve.
|
||||
/// </summary>
|
||||
[Key("sleeve_id")]
|
||||
public int SleeveId { get; set; }
|
||||
public long SleeveId { get; set; }
|
||||
}
|
||||
@@ -2,15 +2,35 @@ using MessagePack;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// Per-link entry in <c>transition_account_data</c>. Production sends three string fields per
|
||||
/// entry even though <c>GameStartCheckTask.Parse</c> only reads <c>social_account_type</c>.
|
||||
/// The extra two are read by adjacent tasks (<c>GetGameDataByTransitionCode</c>,
|
||||
/// <c>GetGameDataBySocialAccountTask</c>) — kept here so the wire matches prod regardless of
|
||||
/// which task ends up consuming the payload.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class TransitionAccountData
|
||||
{
|
||||
/// <summary>
|
||||
/// The social provider's account id (e.g. SteamID as a string). Sent as string on the wire.
|
||||
/// </summary>
|
||||
[Key("social_account_id")]
|
||||
public string SocialAccountId { get; set; }
|
||||
|
||||
public string? SocialAccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>Cute/CuteNetworkDefine.ACCOUNT_TYPE</c> enum, **sent as string** on the wire even
|
||||
/// though it's numeric. <c>GameStartCheckTask.Parse</c> calls <c>.ToInt()</c> on it so
|
||||
/// LitJson coerces transparently — but matching prod's string form makes us safer against
|
||||
/// future client paths that might compare it as a literal.
|
||||
/// 1=GooglePlay, 2=GameCenter, 3=Facebook, 4=DMM, 5=Steam, 6=AppleID.
|
||||
/// </summary>
|
||||
[Key("social_account_type")]
|
||||
public string SocialAccountType { get; set; }
|
||||
|
||||
public string? SocialAccountType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The viewer id this social connection is linked to. Sent as string.
|
||||
/// </summary>
|
||||
[Key("connected_viewer_id")]
|
||||
public string ConnectedViewerId { get; set; }
|
||||
}
|
||||
public string? ConnectedViewerId { get; set; }
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@ public class UserInfo
|
||||
public DateTime LastPlayTime { get; set; }
|
||||
[Key("is_received_two_pick_mission")]
|
||||
public int HasReceivedPickTwoMission { get; set; }
|
||||
/// <summary>
|
||||
/// Birth date as yyyy-MM-dd. Parser does .ToString() on this field (LoadDetail.cs:203).
|
||||
/// Format verified against live capture pending.
|
||||
/// </summary>
|
||||
[Key("birth")]
|
||||
public long Birthday { get; set; }
|
||||
public string Birthday { get; set; } = string.Empty;
|
||||
[Key("selected_emblem_id")]
|
||||
public long SelectedEmblemId { get; set; }
|
||||
[Key("selected_degree_id")]
|
||||
@@ -45,7 +49,7 @@ public class UserInfo
|
||||
this.MaxFriend = viewer.Info.MaxFriends;
|
||||
this.LastPlayTime = viewer.LastLogin;
|
||||
this.HasReceivedPickTwoMission = viewer.MissionData.HasReceivedPickTwoMission ? 1 : 0;
|
||||
this.Birthday = viewer.Info.BirthDate.Ticks;
|
||||
this.Birthday = viewer.Info.BirthDate.ToString("yyyy-MM-dd");
|
||||
this.SelectedEmblemId = viewer.Info.SelectedEmblem.Id;
|
||||
this.SelectedDegreeId = viewer.Info.SelectedDegree.Id;
|
||||
this.MissionChangeTime = viewer.MissionData.MissionChangeTime;
|
||||
@@ -53,4 +57,4 @@ public class UserInfo
|
||||
this.IsOfficial = viewer.Info.IsOfficial ? 1 : 0;
|
||||
this.IsOfficialMarkDisplayed = viewer.Info.IsOfficialMarkDisplayed ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ public class UserLeaderSkin
|
||||
{
|
||||
this.Id = leaderSkin.Id;
|
||||
this.Name = leaderSkin.Name;
|
||||
this.ClassId = leaderSkin.Class.Id;
|
||||
// Class is nullable — BaseDataSeeder maps CSV class_chara_id=0 to null. Fall back to
|
||||
// the FK column (also nullable) and finally 0 for class-agnostic skins.
|
||||
this.ClassId = leaderSkin.Class?.Id ?? leaderSkin.ClassId ?? 0;
|
||||
this.EmoteId = leaderSkin.EmoteId;
|
||||
this.IsOwned = isOwned;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class UserRankInfo
|
||||
[Key("is_master_rank")]
|
||||
public int IsMasterRank { get; set; }
|
||||
[Key("is_grand_master_rank")]
|
||||
public bool IsGrandMasterRank { get; set; }
|
||||
public int IsGrandMasterRank { get; set; }
|
||||
[Key("master_point")]
|
||||
public int MasterPoints { get; set; }
|
||||
[Key("period_grand_master_point")]
|
||||
|
||||
Reference in New Issue
Block a user