refactor(bootstrap): add 7 load-index importers (excluding card lists)
Stage 9B of the bootstrap-seed-refactor: add per-domain importer classes that consume the load-index seed split produced in Stage 9A. New importers (each in its own file under SVSim.Bootstrap/Importers/): - RotationConfigImporter: writes Rotation/Challenge/MyRotationSchedule GameConfig sections (atomic UpsertSection<T> pattern, copied private-static from GlobalsImporter so this importer stands alone post-9C). - MyRotationImporter: settings + abilities (extractor pre-joins on rotation_id). - AvatarAbilityImporter: per-leader_skin_id ability rows. - ArenaSeasonImporter: singleton (Id=1) Take Two arena season. - BattlePassImporter: per-level reward blobs. - DailyLoginBonusImporter: per-bonus-id campaign blobs. - PreReleaseInfoImporter: singleton (Id=1) pre-release window. Seed DTOs under SVSim.Bootstrap/Models/Seed/ mirror the seed JSON via [JsonPropertyName] snake_case. Raw-JSON columns (reward_data, format_info, etc.) use JsonElement on the seed and JsonSerializer.Serialize in the importer. Tests: 7 new happy-path tests in LoadIndexImporterTests.cs (idempotency covered by BattlePass spot-check). Full suite: 382/382 passing (375 + 7). NOT modifying in this stage: GlobalsImporter.cs (Stage 9C strips the old methods), Program.cs (Stage 9C wires up all 9 importers), SVSimTestFactory (Stage 9C). Double-writing on bootstrap is expected and OK during 9B.
This commit is contained in:
37
SVSim.Bootstrap/Importers/ArenaSeasonImporter.cs
Normal file
37
SVSim.Bootstrap/Importers/ArenaSeasonImporter.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton upsert (Id=1) of the active Take Two arena season config from
|
||||
/// <c>seeds/arena-season.json</c>. <c>format_info</c> is preserved verbatim as a jsonb blob.
|
||||
/// </summary>
|
||||
public class ArenaSeasonImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var s = SeedLoader.LoadObject<ArenaSeasonSeed>(Path.Combine(seedDir, "arena-season.json"));
|
||||
if (s is null) return 0;
|
||||
|
||||
var existing = await context.ArenaSeasons.FirstOrDefaultAsync(e => e.Id == 1);
|
||||
var entry = existing ?? new ArenaSeasonConfig { Id = 1 };
|
||||
entry.Mode = s.Mode;
|
||||
entry.Enable = s.Enable;
|
||||
entry.Cost = s.Cost;
|
||||
entry.RupyCost = s.RupyCost;
|
||||
entry.TicketCost = s.TicketCost;
|
||||
entry.IsJoin = s.IsJoin;
|
||||
entry.FormatInfo = s.FormatInfo.ValueKind == JsonValueKind.Undefined
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(s.FormatInfo);
|
||||
if (existing is null) context.ArenaSeasons.Add(entry);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[ArenaSeasonImporter] {(existing is null ? "+1" : "~1")}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
41
SVSim.Bootstrap/Importers/AvatarAbilityImporter.cs
Normal file
41
SVSim.Bootstrap/Importers/AvatarAbilityImporter.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent upsert of Avatar (Hero) ability rows from <c>seeds/avatar-abilities.json</c>.
|
||||
/// Keyed by leader_skin_id. Ability / passive-ability DSL strings are preserved verbatim.
|
||||
/// </summary>
|
||||
public class AvatarAbilityImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var seed = SeedLoader.LoadList<AvatarAbilitySeed>(Path.Combine(seedDir, "avatar-abilities.json"));
|
||||
if (seed.Count == 0) return 0;
|
||||
|
||||
var existing = await context.AvatarAbilities.ToDictionaryAsync(e => e.Id);
|
||||
int created = 0, updated = 0;
|
||||
foreach (var s in seed)
|
||||
{
|
||||
if (s.Id == 0) continue;
|
||||
var entry = existing.TryGetValue(s.Id, out var ex) ? ex : new AvatarAbilityEntry { Id = s.Id };
|
||||
entry.BattleStartFirstPlayerTurnBp = s.BattleStartFirstPlayerTurnBp;
|
||||
entry.BattleStartSecondPlayerTurnBp = s.BattleStartSecondPlayerTurnBp;
|
||||
entry.BattleStartMaxLife = s.BattleStartMaxLife;
|
||||
entry.AbilityCost = s.AbilityCost;
|
||||
entry.Ability = s.Ability;
|
||||
entry.PassiveAbility = s.PassiveAbility;
|
||||
entry.AbilityDesc = s.AbilityDesc;
|
||||
entry.PassiveAbilityDesc = s.PassiveAbilityDesc;
|
||||
if (ex is null) { context.AvatarAbilities.Add(entry); existing[s.Id] = entry; created++; }
|
||||
else updated++;
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[AvatarAbilityImporter] +{created}/~{updated}");
|
||||
return created + updated;
|
||||
}
|
||||
}
|
||||
37
SVSim.Bootstrap/Importers/BattlePassImporter.cs
Normal file
37
SVSim.Bootstrap/Importers/BattlePassImporter.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent upsert of battle-pass level rows from <c>seeds/battle-pass-levels.json</c>.
|
||||
/// Per-level <c>reward_data</c> blob preserved verbatim (shape varies per level).
|
||||
/// </summary>
|
||||
public class BattlePassImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var seed = SeedLoader.LoadList<BattlePassLevelSeed>(Path.Combine(seedDir, "battle-pass-levels.json"));
|
||||
if (seed.Count == 0) return 0;
|
||||
|
||||
var existing = await context.BattlePassLevels.ToDictionaryAsync(e => e.Id);
|
||||
int created = 0, updated = 0;
|
||||
foreach (var s in seed)
|
||||
{
|
||||
if (s.Id == 0) continue;
|
||||
var entry = existing.TryGetValue(s.Id, out var ex) ? ex : new BattlePassLevelEntry { Id = s.Id };
|
||||
entry.RewardData = s.RewardData.ValueKind == JsonValueKind.Undefined
|
||||
? "{}"
|
||||
: JsonSerializer.Serialize(s.RewardData);
|
||||
if (ex is null) { context.BattlePassLevels.Add(entry); existing[s.Id] = entry; created++; }
|
||||
else updated++;
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[BattlePassImporter] +{created}/~{updated}");
|
||||
return created + updated;
|
||||
}
|
||||
}
|
||||
37
SVSim.Bootstrap/Importers/DailyLoginBonusImporter.cs
Normal file
37
SVSim.Bootstrap/Importers/DailyLoginBonusImporter.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent upsert of daily-login-bonus campaign rows from <c>seeds/daily-login-bonus.json</c>.
|
||||
/// <c>bonus_data</c> array preserved verbatim — prod observed empty arrays outside active events.
|
||||
/// </summary>
|
||||
public class DailyLoginBonusImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var seed = SeedLoader.LoadList<DailyLoginBonusSeed>(Path.Combine(seedDir, "daily-login-bonus.json"));
|
||||
if (seed.Count == 0) return 0;
|
||||
|
||||
var existing = await context.DailyLoginBonuses.ToDictionaryAsync(e => e.Id);
|
||||
int created = 0, updated = 0;
|
||||
foreach (var s in seed)
|
||||
{
|
||||
if (s.Id == 0) continue;
|
||||
var entry = existing.TryGetValue(s.Id, out var ex) ? ex : new DailyLoginBonusEntry { Id = s.Id };
|
||||
entry.BonusData = s.BonusData.ValueKind == JsonValueKind.Undefined
|
||||
? "[]"
|
||||
: JsonSerializer.Serialize(s.BonusData);
|
||||
if (ex is null) { context.DailyLoginBonuses.Add(entry); existing[s.Id] = entry; created++; }
|
||||
else updated++;
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[DailyLoginBonusImporter] +{created}/~{updated}");
|
||||
return created + updated;
|
||||
}
|
||||
}
|
||||
53
SVSim.Bootstrap/Importers/MyRotationImporter.cs
Normal file
53
SVSim.Bootstrap/Importers/MyRotationImporter.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent upsert of MyRotation reference data: settings (per rotation_id) +
|
||||
/// abilities (per ability_id). Seeds come from <c>seeds/my-rotation-settings.json</c> and
|
||||
/// <c>seeds/my-rotation-abilities.json</c>; the extractor pre-joins the original wire's three
|
||||
/// dicts (setting, reprinted, restricted) on rotation_id, so the importer just iterates.
|
||||
/// </summary>
|
||||
public class MyRotationImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var settings = SeedLoader.LoadList<MyRotationSettingSeed>(Path.Combine(seedDir, "my-rotation-settings.json"));
|
||||
var abilities = SeedLoader.LoadList<MyRotationAbilitySeed>(Path.Combine(seedDir, "my-rotation-abilities.json"));
|
||||
|
||||
if (settings.Count == 0 && abilities.Count == 0) return 0;
|
||||
|
||||
int sCreated = 0, sUpdated = 0;
|
||||
var existingSettings = await context.MyRotationSettings.ToDictionaryAsync(e => e.Id);
|
||||
foreach (var s in settings)
|
||||
{
|
||||
if (s.Id == 0) continue;
|
||||
var entry = existingSettings.TryGetValue(s.Id, out var ex) ? ex : new MyRotationSettingEntry { Id = s.Id };
|
||||
entry.CardSetIdsCsv = s.CardSetIdsCsv;
|
||||
entry.AbilitiesCsv = s.AbilitiesCsv;
|
||||
entry.ReprintedCardIds = s.ReprintedCardIds;
|
||||
entry.RestrictedCardIds = s.RestrictedCardIds;
|
||||
if (ex is null) { context.MyRotationSettings.Add(entry); existingSettings[s.Id] = entry; sCreated++; }
|
||||
else sUpdated++;
|
||||
}
|
||||
|
||||
int aCreated = 0, aUpdated = 0;
|
||||
var existingAbilities = await context.MyRotationAbilities.ToDictionaryAsync(e => e.Id);
|
||||
foreach (var s in abilities)
|
||||
{
|
||||
if (s.Id == 0) continue;
|
||||
var entry = existingAbilities.TryGetValue(s.Id, out var ex) ? ex : new MyRotationAbilityEntry { Id = s.Id };
|
||||
entry.Data = JsonSerializer.Serialize(s.Data);
|
||||
if (ex is null) { context.MyRotationAbilities.Add(entry); existingAbilities[s.Id] = entry; aCreated++; }
|
||||
else aUpdated++;
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[MyRotationImporter] settings +{sCreated}/~{sUpdated}, abilities +{aCreated}/~{aUpdated}");
|
||||
return sCreated + sUpdated + aCreated + aUpdated;
|
||||
}
|
||||
}
|
||||
46
SVSim.Bootstrap/Importers/PreReleaseInfoImporter.cs
Normal file
46
SVSim.Bootstrap/Importers/PreReleaseInfoImporter.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
using static SVSim.Bootstrap.Importers.ImporterBase;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton upsert (Id=1) of the pre-release window from <c>seeds/pre-release-info.json</c>.
|
||||
/// Card-id list / dict blobs are preserved verbatim into their jsonb columns; date strings go
|
||||
/// through <see cref="ImporterBase.ParseWireDateTime"/>.
|
||||
/// </summary>
|
||||
public class PreReleaseInfoImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
var s = SeedLoader.LoadObject<PreReleaseInfoSeed>(Path.Combine(seedDir, "pre-release-info.json"));
|
||||
if (s is null) return 0;
|
||||
|
||||
var existing = await context.PreReleaseInfos.FirstOrDefaultAsync(e => e.Id == 1);
|
||||
var entry = existing ?? new PreReleaseInfo { Id = 1 };
|
||||
entry.PreReleaseId = s.PreReleaseId;
|
||||
entry.NextCardSetId = s.NextCardSetId;
|
||||
entry.StartTime = ParseWireDateTime(s.StartTime);
|
||||
entry.EndTime = ParseWireDateTime(s.EndTime);
|
||||
entry.DisplayEndTime = ParseWireDateTime(s.DisplayEndTime);
|
||||
entry.FreeMatchStartTime = ParseWireDateTime(s.FreeMatchStartTime);
|
||||
entry.CardMasterId = s.CardMasterId;
|
||||
entry.DefaultCardMasterId = s.DefaultCardMasterId;
|
||||
entry.PreReleaseCardMasterId = s.PreReleaseCardMasterId;
|
||||
entry.IsPreRotationFreeMatchTerm = s.IsPreRotationFreeMatchTerm;
|
||||
entry.RotationCardSetIdList = s.RotationCardSetIdList.ValueKind == JsonValueKind.Undefined
|
||||
? "[]" : JsonSerializer.Serialize(s.RotationCardSetIdList);
|
||||
entry.ReprintedBaseCardIds = s.ReprintedBaseCardIds.ValueKind == JsonValueKind.Undefined
|
||||
? "{}" : JsonSerializer.Serialize(s.ReprintedBaseCardIds);
|
||||
entry.LatestReprintedBaseCardIds = s.LatestReprintedBaseCardIds.ValueKind == JsonValueKind.Undefined
|
||||
? "{}" : JsonSerializer.Serialize(s.LatestReprintedBaseCardIds);
|
||||
if (existing is null) context.PreReleaseInfos.Add(entry);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[PreReleaseInfoImporter] {(existing is null ? "+1" : "~1")}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
102
SVSim.Bootstrap/Importers/RotationConfigImporter.cs
Normal file
102
SVSim.Bootstrap/Importers/RotationConfigImporter.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SVSim.Bootstrap.Models.Seed;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Models.Config;
|
||||
using static SVSim.Bootstrap.Importers.ImporterBase;
|
||||
|
||||
namespace SVSim.Bootstrap.Importers;
|
||||
|
||||
/// <summary>
|
||||
/// Writes three <c>GameConfigSection</c> rows from the load-index seed split:
|
||||
/// <c>seeds/rotation-config.json</c> → Rotation, <c>seeds/challenge-config.json</c> → Challenge,
|
||||
/// <c>seeds/my-rotation-schedule.json</c> → MyRotationSchedule. Atomic section pattern: read the
|
||||
/// existing section row (or shipped defaults), mutate the deserialized POCO, write back to
|
||||
/// <c>ValueJson</c>. Re-runnable; rows missing from the seed leave the section row untouched.
|
||||
/// </summary>
|
||||
public class RotationConfigImporter
|
||||
{
|
||||
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
|
||||
{
|
||||
int touched = 0;
|
||||
|
||||
var rot = SeedLoader.LoadObject<RotationConfigSeed>(Path.Combine(seedDir, "rotation-config.json"));
|
||||
if (rot is not null)
|
||||
{
|
||||
await UpsertSection<RotationConfig>(context, RotationConfig.ShippedDefaults, c =>
|
||||
{
|
||||
c.TsRotationId = rot.TsRotationId;
|
||||
c.IsBattlePassPeriod = rot.IsBattlePassPeriod;
|
||||
c.IsBeginnerMission = rot.IsBeginnerMission;
|
||||
c.CardSetIdForResourceDlView = rot.CardSetIdForResourceDlView;
|
||||
});
|
||||
touched++;
|
||||
}
|
||||
|
||||
var cc = SeedLoader.LoadObject<ChallengeConfigSeed>(Path.Combine(seedDir, "challenge-config.json"));
|
||||
if (cc is not null)
|
||||
{
|
||||
await UpsertSection<ChallengeConfig>(context, ChallengeConfig.ShippedDefaults, c =>
|
||||
{
|
||||
c.UseTwoPickPremiumCard = cc.UseTwoPickPremiumCard;
|
||||
c.TwoPickSleeveId = cc.TwoPickSleeveId;
|
||||
});
|
||||
touched++;
|
||||
}
|
||||
|
||||
var schedule = SeedLoader.LoadObject<MyRotationScheduleSeed>(Path.Combine(seedDir, "my-rotation-schedule.json"));
|
||||
if (schedule?.Gathering is not null && schedule.FreeBattle is not null)
|
||||
{
|
||||
var gBegin = ParseWireDateTime(schedule.Gathering.Begin);
|
||||
var gEnd = ParseWireDateTime(schedule.Gathering.End);
|
||||
var fBegin = ParseWireDateTime(schedule.FreeBattle.Begin);
|
||||
var fEnd = ParseWireDateTime(schedule.FreeBattle.End);
|
||||
// Only commit when both windows parsed to real DateTimes — a malformed/0001 value
|
||||
// would silently lock the MyRotation feature off (the original bug the section fixed).
|
||||
if (gBegin != DateTime.MinValue && gEnd != DateTime.MinValue
|
||||
&& fBegin != DateTime.MinValue && fEnd != DateTime.MinValue)
|
||||
{
|
||||
await UpsertSection<MyRotationScheduleConfig>(context, MyRotationScheduleConfig.ShippedDefaults, c =>
|
||||
{
|
||||
c.Gathering = new ScheduleWindow { Begin = gBegin, End = gEnd };
|
||||
c.FreeBattle = new ScheduleWindow { Begin = fBegin, End = fEnd };
|
||||
});
|
||||
touched++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine("[RotationConfigImporter] my-rotation-schedule.json windows malformed — keeping existing/shipped MyRotationSchedule.");
|
||||
}
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine($"[RotationConfigImporter] sections={touched}");
|
||||
return touched;
|
||||
}
|
||||
|
||||
// Verbatim copy of GlobalsImporter.UpsertSection<T>. Kept private-static here so this
|
||||
// importer can stand alone after Stage 9C strips the GlobalsImporter copy.
|
||||
private static async Task UpsertSection<T>(SVSimDbContext context, Func<T> shippedDefaults, Action<T> mutate)
|
||||
where T : class, new()
|
||||
{
|
||||
var sectionName = typeof(T).GetCustomAttributes(typeof(ConfigSectionAttribute), inherit: false)
|
||||
.Cast<ConfigSectionAttribute>().FirstOrDefault()?.Name
|
||||
?? throw new InvalidOperationException($"{typeof(T).Name} is missing [ConfigSection].");
|
||||
|
||||
var row = await context.GameConfigs.FirstOrDefaultAsync(s => s.SectionName == sectionName);
|
||||
T value;
|
||||
if (row is null)
|
||||
{
|
||||
value = shippedDefaults();
|
||||
row = new GameConfigSection { SectionName = sectionName };
|
||||
context.GameConfigs.Add(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = JsonSerializer.Deserialize<T>(row.ValueJson) ?? shippedDefaults();
|
||||
}
|
||||
mutate(value);
|
||||
row.ValueJson = JsonSerializer.Serialize(value);
|
||||
}
|
||||
}
|
||||
20
SVSim.Bootstrap/Models/Seed/ArenaSeasonSeed.cs
Normal file
20
SVSim.Bootstrap/Models/Seed/ArenaSeasonSeed.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors <c>seeds/arena-season.json</c>. Singleton (id=1) holding the Take Two arena season.
|
||||
/// <c>format_info</c> is a nested JSON object stored verbatim as the entity's <c>FormatInfo</c> jsonb.
|
||||
/// </summary>
|
||||
public sealed class ArenaSeasonSeed
|
||||
{
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
[JsonPropertyName("mode")] public int Mode { get; set; }
|
||||
[JsonPropertyName("enable")] public int Enable { get; set; }
|
||||
[JsonPropertyName("cost")] public ulong Cost { get; set; }
|
||||
[JsonPropertyName("rupy_cost")] public ulong RupyCost { get; set; }
|
||||
[JsonPropertyName("ticket_cost")] public int TicketCost { get; set; }
|
||||
[JsonPropertyName("is_join")] public bool IsJoin { get; set; }
|
||||
[JsonPropertyName("format_info")] public JsonElement FormatInfo { get; set; }
|
||||
}
|
||||
17
SVSim.Bootstrap/Models/Seed/AvatarAbilitySeed.cs
Normal file
17
SVSim.Bootstrap/Models/Seed/AvatarAbilitySeed.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>Mirrors <c>seeds/avatar-abilities.json</c>. One row per leader_skin_id.</summary>
|
||||
public sealed class AvatarAbilitySeed
|
||||
{
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
[JsonPropertyName("battle_start_first_player_turn_bp")] public int BattleStartFirstPlayerTurnBp { get; set; }
|
||||
[JsonPropertyName("battle_start_second_player_turn_bp")] public int BattleStartSecondPlayerTurnBp { get; set; }
|
||||
[JsonPropertyName("battle_start_max_life")] public int BattleStartMaxLife { get; set; }
|
||||
[JsonPropertyName("ability_cost")] public string AbilityCost { get; set; } = "";
|
||||
[JsonPropertyName("ability")] public string Ability { get; set; } = "";
|
||||
[JsonPropertyName("passive_ability")] public string PassiveAbility { get; set; } = "";
|
||||
[JsonPropertyName("ability_desc")] public string AbilityDesc { get; set; } = "";
|
||||
[JsonPropertyName("passive_ability_desc")] public string PassiveAbilityDesc { get; set; } = "";
|
||||
}
|
||||
11
SVSim.Bootstrap/Models/Seed/BattlePassLevelSeed.cs
Normal file
11
SVSim.Bootstrap/Models/Seed/BattlePassLevelSeed.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>Mirrors <c>seeds/battle-pass-levels.json</c>. <c>reward_data</c> preserved verbatim.</summary>
|
||||
public sealed class BattlePassLevelSeed
|
||||
{
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
[JsonPropertyName("reward_data")] public JsonElement RewardData { get; set; }
|
||||
}
|
||||
11
SVSim.Bootstrap/Models/Seed/DailyLoginBonusSeed.cs
Normal file
11
SVSim.Bootstrap/Models/Seed/DailyLoginBonusSeed.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>Mirrors <c>seeds/daily-login-bonus.json</c>. <c>bonus_data</c> preserved verbatim.</summary>
|
||||
public sealed class DailyLoginBonusSeed
|
||||
{
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
[JsonPropertyName("bonus_data")] public JsonElement BonusData { get; set; }
|
||||
}
|
||||
11
SVSim.Bootstrap/Models/Seed/MyRotationAbilitySeed.cs
Normal file
11
SVSim.Bootstrap/Models/Seed/MyRotationAbilitySeed.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>Mirrors <c>seeds/my-rotation-abilities.json</c>. <c>data</c> is preserved as raw JSON.</summary>
|
||||
public sealed class MyRotationAbilitySeed
|
||||
{
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
[JsonPropertyName("data")] public JsonElement Data { get; set; }
|
||||
}
|
||||
18
SVSim.Bootstrap/Models/Seed/MyRotationSettingSeed.cs
Normal file
18
SVSim.Bootstrap/Models/Seed/MyRotationSettingSeed.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors <c>seeds/my-rotation-settings.json</c>. The extractor pre-joins
|
||||
/// <c>my_rotation_info.{setting, reprinted_base_card_ids, restricted_base_card_id_list}</c> on
|
||||
/// rotation_id into one flat list. <c>reprinted_card_ids</c> and <c>restricted_card_ids</c> are
|
||||
/// pre-serialized JSON strings (verbatim from the wire) — the importer stores them verbatim.
|
||||
/// </summary>
|
||||
public sealed class MyRotationSettingSeed
|
||||
{
|
||||
[JsonPropertyName("id")] public int Id { get; set; }
|
||||
[JsonPropertyName("card_set_ids_csv")] public string CardSetIdsCsv { get; set; } = "";
|
||||
[JsonPropertyName("abilities_csv")] public string AbilitiesCsv { get; set; } = "";
|
||||
[JsonPropertyName("reprinted_card_ids")] public string ReprintedCardIds { get; set; } = "[]";
|
||||
[JsonPropertyName("restricted_card_ids")] public string RestrictedCardIds { get; set; } = "[]";
|
||||
}
|
||||
25
SVSim.Bootstrap/Models/Seed/PreReleaseInfoSeed.cs
Normal file
25
SVSim.Bootstrap/Models/Seed/PreReleaseInfoSeed.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors <c>seeds/pre-release-info.json</c>. Singleton (id=1). Card-id lists are kept as raw
|
||||
/// JSON elements so they round-trip verbatim into the entity's jsonb columns.
|
||||
/// </summary>
|
||||
public sealed class PreReleaseInfoSeed
|
||||
{
|
||||
[JsonPropertyName("pre_release_id")] public string PreReleaseId { get; set; } = "";
|
||||
[JsonPropertyName("next_card_set_id")] public string NextCardSetId { get; set; } = "";
|
||||
[JsonPropertyName("start_time")] public string StartTime { get; set; } = "";
|
||||
[JsonPropertyName("end_time")] public string EndTime { get; set; } = "";
|
||||
[JsonPropertyName("display_end_time")] public string DisplayEndTime { get; set; } = "";
|
||||
[JsonPropertyName("free_match_start_time")] public string FreeMatchStartTime { get; set; } = "";
|
||||
[JsonPropertyName("card_master_id")] public int CardMasterId { get; set; }
|
||||
[JsonPropertyName("default_card_master_id")] public string DefaultCardMasterId { get; set; } = "";
|
||||
[JsonPropertyName("pre_release_card_master_id")] public string PreReleaseCardMasterId { get; set; } = "";
|
||||
[JsonPropertyName("is_pre_rotation_free_match_term")] public bool IsPreRotationFreeMatchTerm { get; set; }
|
||||
[JsonPropertyName("rotation_card_set_id_list")] public JsonElement RotationCardSetIdList { get; set; }
|
||||
[JsonPropertyName("reprinted_base_card_ids")] public JsonElement ReprintedBaseCardIds { get; set; }
|
||||
[JsonPropertyName("latest_reprinted_base_card_ids")] public JsonElement LatestReprintedBaseCardIds { get; set; }
|
||||
}
|
||||
41
SVSim.Bootstrap/Models/Seed/RotationConfigSeed.cs
Normal file
41
SVSim.Bootstrap/Models/Seed/RotationConfigSeed.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SVSim.Bootstrap.Models.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors <c>seeds/rotation-config.json</c>. Drives the Rotation <c>GameConfigSection</c>.
|
||||
/// Note: <c>rotation_card_set_ids</c> is the rotation CardSet flag list — consumed by
|
||||
/// RotationFlagUpdater in Stage 9C, not by RotationConfigImporter.
|
||||
/// </summary>
|
||||
public sealed class RotationConfigSeed
|
||||
{
|
||||
[JsonPropertyName("ts_rotation_id")] public string TsRotationId { get; set; } = "";
|
||||
[JsonPropertyName("is_battle_pass_period")] public bool IsBattlePassPeriod { get; set; }
|
||||
[JsonPropertyName("is_beginner_mission")] public bool IsBeginnerMission { get; set; }
|
||||
[JsonPropertyName("card_set_id_for_resource_dl_view")] public int CardSetIdForResourceDlView { get; set; }
|
||||
[JsonPropertyName("rotation_card_set_ids")] public List<int> RotationCardSetIds { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>Mirrors <c>seeds/challenge-config.json</c>. Drives the Challenge <c>GameConfigSection</c>.</summary>
|
||||
public sealed class ChallengeConfigSeed
|
||||
{
|
||||
[JsonPropertyName("use_two_pick_premium_card")] public bool UseTwoPickPremiumCard { get; set; }
|
||||
[JsonPropertyName("two_pick_sleeve_id")] public long TwoPickSleeveId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors <c>seeds/my-rotation-schedule.json</c>. Drives the MyRotationSchedule
|
||||
/// <c>GameConfigSection</c>. The extractor pre-joins <c>gathering</c> and <c>free_battle</c>
|
||||
/// from <c>my_rotation_info.schedules</c> into two top-level fields.
|
||||
/// </summary>
|
||||
public sealed class MyRotationScheduleSeed
|
||||
{
|
||||
[JsonPropertyName("gathering")] public ScheduleWindowSeed? Gathering { get; set; }
|
||||
[JsonPropertyName("free_battle")] public ScheduleWindowSeed? FreeBattle { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ScheduleWindowSeed
|
||||
{
|
||||
[JsonPropertyName("begin")] public string Begin { get; set; } = "";
|
||||
[JsonPropertyName("end")] public string End { get; set; } = "";
|
||||
}
|
||||
Reference in New Issue
Block a user