Compare commits

...

37 Commits

Author SHA1 Message Date
gamer147
c303d3040d fix(bp): convert seed JST dates to UTC for Postgres timestamp-with-tz
Npgsql rejects DateTimeOffset writes to timestamp-with-tz unless offset
is zero. Caught by manual bootstrap against a real Postgres DB; SQLite
test provider didn't enforce this. Converting to UTC post-parse is
semantically lossless — DateTimeOffset comparisons are instant-based.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:05:48 -04:00
gamer147
c7dfd43daa review(bp): doc fixes + sales_period_info non-nullable + drop checked cast + TODO
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 00:00:24 -04:00
gamer147
9147ab0ec7 feat(bp): AddPointsAsync plumbing + level-cross auto-grant + weekly cap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:47:30 -04:00
gamer147
4438e81e37 review(bp): post-state reward_list from RewardGrantService, not deltas
Crystal synthesis in BuyPremiumAsync is now unconditional: always remove
any crystal entry ApplyAsync may have added, then append the fresh
post-deduction total. Prevents stale on-screen balances when a retroactive
grant also touches crystal (or when no grants fire and the conditional
guard would have been the only crystal entry).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:42:06 -04:00
gamer147
2cb8c271a8 feat(bp): /battle_pass/buy — crystal-cost + retroactive premium grants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:36:18 -04:00
gamer147
0ceab721e9 feat(bp): /battle_pass/item_list — derives product per active season
Adds BattlePassSalesPeriodInfoDto, BattlePassProductDto, BattlePassItemListResponse DTOs,
GetItemListAsync on BattlePassService (one product if not premium + CanPurchase, empty if
already premium or off-season), and the /battle_pass/item_list controller action.
2 new integration tests; all 408 pass.
2026-05-26 23:26:46 -04:00
gamer147
d877febcb8 review(bp): move SaveChanges into repo with race protection; JST constant
GetOrCreateProgressAsync now persists the new row itself and catches
DbUpdateException on unique-constraint violations — concurrent /info
calls no longer throw 500s. BattlePassService no longer calls
SaveChangesAsync after the get-or-create. FormatWireDate uses a named
JstOffset constant instead of an inline TimeSpan.FromHours(9).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:22:48 -04:00
gamer147
8a35f8c40b feat(bp): /battle_pass/info — service + controller + 3 tests
Also fixes BattlePassRepository.GetActiveSeasonAsync to use client-side
DateTimeOffset filtering (SQLite provider cannot translate DateTimeOffset
comparisons in LINQ WHERE/ORDER BY clauses).
2026-05-26 23:14:26 -04:00
gamer147
6ed61ea9f1 feat(bp): /battle_pass/info response DTOs — string-typed wire numerics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:05:51 -04:00
gamer147
7abdfe27cb review(bp): drop fragile cast, thread CT, internalize cache reset
- IndexResponse.BattlePassLevelInfo widened to IReadOnlyDictionary<string,BattlePassLevel>?
  so any IReadOnlyDictionary impl (FrozenDictionary, wrapper, etc.) serializes correctly
  instead of silently null-ing via a failed as-cast
- LoadController.Index now takes CancellationToken ct and threads it to GetLevelCurveAsync
  instead of CancellationToken.None
- BattlePassRepository.ResetLevelCurveCache changed from public to internal; added
  InternalsVisibleTo("SVSim.UnitTests") to SVSim.Database.csproj (was absent)
2026-05-26 23:01:26 -04:00
gamer147
9bec1df52f feat(load-index): populate battle_pass_level_info from IBattlePassService
Wire IBattlePassService.GetLevelCurveAsync into LoadController so /load/index
emits the 100-entry battle_pass_level_info dict when levels are seeded.

Also adds BattlePassRepository.ResetLevelCurveCache() to bust the process-level
static cache in tests that seed levels after earlier HTTP calls have primed it
with an empty list, and updates SVSimTestFactory.SeedGlobalsAsync + the stale
Index_surfaces_seeded_globals_after_bootstrap assertion accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:56:41 -04:00
gamer147
9043e20646 feat(bp): IBattlePassService skeleton + level-curve method + DI 2026-05-26 22:49:30 -04:00
gamer147
1420c60486 feat(bp): repositories + identity generation for runtime-inserted tables
Add ValueGeneratedOnAdd to ViewerBattlePassProgress.Id and
ViewerBattlePassClaims.Id so Postgres generates IDENTITY values at
runtime. Regenerate AddBattlePass migration in-place to include the
IdentityByDefaultColumn annotations. Add IBattlePassRepository /
BattlePassRepository (season lookup + level-curve cache) and
IViewerBattlePassRepository / ViewerBattlePassRepository
(get-or-create progress, claim reads/writes).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:40:45 -04:00
gamer147
44da54c418 review(bp): wire new importers into SeedGlobalsAsync + consistent test orphan Id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:36:02 -04:00
gamer147
61a9133855 feat(bp): season + reward importers, idempotent + authoritative-per-season 2026-05-26 22:28:41 -04:00
gamer147
d661b6f44c seed(bp): regenerate from extract-battle-pass.py — season 23 + 143 rewards 2026-05-26 22:16:37 -04:00
gamer147
3f784f4294 feat(bp): wire 4 new entities into DbContext + AddBattlePass migration
Adds DbSets and OnModelCreating config for BattlePassSeasonEntry,
BattlePassRewardEntry, ViewerBattlePassProgressEntry, and
ViewerBattlePassClaimEntry; generates migration 20260527021011_AddBattlePass
with DDL-only CreateTable + CreateIndex calls and no InsertData.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:12:55 -04:00
gamer147
faa8c0e6dd feat(bp): add ViewerBattlePassProgressEntry + ViewerBattlePassClaimEntry 2026-05-26 22:07:05 -04:00
gamer147
34de3d53ad feat(bp): add BattlePassSeasonEntry + BattlePassRewardEntry + BattlePassTrack enum 2026-05-26 22:04:10 -04:00
gamer147
8f07afce83 review(bp): consistent dict key + log on empty seed 2026-05-26 22:02:37 -04:00
gamer147
95b8f39ea5 refactor(bp): flatten BattlePassLevelEntry — drop misnamed RewardData jsonb
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:57:47 -04:00
gamer147
141f34f817 chore(bootstrap): refresh stale GlobalsImporter references in docs/test names 2026-05-26 16:44:54 -04:00
gamer147
c02991a5c2 refactor(bootstrap): finalize seed migration; remove GlobalsImporter and prod-captures plumbing
Final cleanup of the bootstrap-seed refactor (Task 10 of 10):

- Delete the GlobalsImporter no-op stub and its two remaining call sites
  (Program.cs and SVSimTestFactory.cs). All work has moved to per-domain
  importers since Task 9.
- Drop the --captures CLI flag and CapturesDir / shippedCaptures plumbing
  from Program.cs (BootstrapOptions, ParseArgs, PrintUsage). Bootstrap input
  is now cards.json + reference CSVs + per-table seed JSON; no more
  prod-captures directory.
- Shrink ImporterBase from 141 to 23 lines: LoadCapture, Serialize,
  Upsert<T,TKey>, GetInt/GetString/GetBool/GetLong/GetULong all had zero
  callers after the seed migration. Only ParseWireDateTime survives (still
  used by PaymentItemImporter and MyPageGlobalsImporter for prod-shaped
  timestamp strings).
- Remove the prod-captures Content Include glob from SVSim.Bootstrap.csproj
  and both prod-captures globs (production + test-fixture overlay) from
  SVSim.UnitTests.csproj. Test fixtures now overlay production seeds at
  Data/seeds/ via the Task 7 layout exclusively.

Build clean; 391/391 unit tests passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 16:06:46 -04:00
gamer147
d14a0be2c8 refactor(bootstrap): finalize load-index migration; GlobalsImporter is now a stub
Stage 9C of the bootstrap-seed-refactor:

- Add 6 seed DTOs for the card-id-keyed load-index tables (SpotCard,
  ReprintedCard, UnlimitedRestriction, LoadingExclusionCard, MaintenanceCard,
  FeatureMaintenance).
- Add CardListsImporter: idempotent upsert of the 6 tables, sharing one
  Cards FK set for orphan-warning. FeatureMaintenances clear-and-rewrites
  (synthetic ordinal Id; no natural key).
- Add RotationFlagUpdater: reads RotationConfig.RotationCardSetIds from the
  GameConfigs section (populated by RotationConfigImporter) and flips
  CardSet.IsInRotation to match.
- Add RotationConfig.RotationCardSetIds list property + wire it through
  RotationConfigImporter. No migration needed (sections are JSON blobs).
- RotationConfigImporter: use legacy local-kind DateTime parse for schedule
  windows so the JSON round-trip stays byte-equivalent to GlobalsImporter.
- Strip GlobalsImporter down to a no-op stub (Task 10 will delete it).
- Wire all 9 new importers into Program.cs and SVSimTestFactory.SeedGlobalsAsync,
  in the order RotationConfigImporter -> ... -> CardListsImporter -> RotationFlagUpdater.
- Delete prod-captures/load-index-2026-05-23.json.
- Add CardListsImporterTests covering each sub-table, idempotency,
  empty-seed handling, orphan-warning, and the clear-and-rewrite path.

Tests: 391 passing (382 baseline + 9 new).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 15:46:36 -04:00
gamer147
87d0001569 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.
2026-05-26 15:29:57 -04:00
gamer147
8dbd52da54 data(load-index): generated load-index seed files
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 15:21:50 -04:00
gamer147
34ed8788a4 refactor(bootstrap): migrate build-deck catalog to seed file 2026-05-26 15:16:36 -04:00
gamer147
a71bf6c62b refactor(bootstrap): migrate /pack/info to seed file 2026-05-26 15:02:49 -04:00
gamer147
83298a2d47 refactor(bootstrap): migrate default decks to seed file
Extracts /deck/info's default_deck_list into seeds/default-decks.json
via the new extract-default-decks.ps1 PowerShell script and imports
through DefaultDeckImporter. The importer carries the same orphan-
card-id warning the old GlobalsImporter path emitted; production cards
yield 0 orphans. WarnOrphans stays inside GlobalsImporter for now —
SpotCards/ReprintedCards/UnlimitedRestrictions/LoadingExclusionCards
still use it until Task 9.

Part of the bootstrap seed refactor (Task 6).
2026-05-26 14:44:21 -04:00
gamer147
a5e4f35c32 refactor(bootstrap): migrate mypage-index globals to seed files 2026-05-26 14:31:25 -04:00
gamer147
0da8ebe1c1 refactor(bootstrap): migrate basic puzzles to seed files
Replaces GlobalsImporter's ImportPuzzleGroups/Puzzles/Missions methods (plus the
DeriveTargetPuzzleGroupId regex helper) with a dedicated PuzzleImporter that
reads three flat seed JSONs (puzzle-groups, puzzles, puzzle-missions) produced
by the Python extractor. Groups run before puzzles to satisfy the FK; missions
upsert by sequential id. Wired into Program.cs and SVSimTestFactory after
PaymentItemImporter so existing GlobalsImporterPuzzleTests continue to pass
unchanged via SeedGlobalsAsync. The original prod-capture JSONs are deleted now
that the seeds are authoritative.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:16:32 -04:00
gamer147
f66d20e039 fix(payment-items): use ImporterBase.ParseWireDateTime 2026-05-26 14:08:40 -04:00
gamer147
c23c56d46c refactor(bootstrap): migrate payment items to seed file
Lifts ImportPaymentItems out of GlobalsImporter into a dedicated
PaymentItemImporter driven by Data/seeds/payment-items.json. Wired
into Program.cs and SVSimTestFactory.SeedGlobalsAsync after
PracticeOpponentImporter. Drops the prod-capture file in favor of
the extractor pipeline.

Canonical 4-test suite (basic, idempotent, leave-untouched, skip-zero)
keeps the dict-in-sync upsert pattern Task 2 established.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:59:50 -04:00
gamer147
0b41474968 fix(practice-opponents): guard duplicate ids + cover edge cases 2026-05-26 13:52:12 -04:00
gamer147
40b0de1d51 refactor(bootstrap): migrate practice opponents to seed file
Move /practice/info handling out of GlobalsImporter into a dedicated
PracticeOpponentImporter that reads a normalized JSON seed file
generated by data_dumps/extract/extract-practice-opponents.ps1.
2026-05-26 13:42:59 -04:00
gamer147
7ec4892d73 fix(bootstrap): use Content Include for seeds glob 2026-05-26 13:31:42 -04:00
gamer147
f2a1263198 refactor(bootstrap): add seed loader + extractor scaffolding 2026-05-26 13:24:49 -04:00
173 changed files with 23583 additions and 17017 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"data_headers":{"sid":"079f239bb83de281ebc6b2f68dbb2cd11779683743","short_udid":411054851,"viewer_id":906243102,"servertime":1779683743,"result_code":1},"data":[{"mission_name":"Clear all Dragoncraft and Portalcraft puzzles puzzles in the Special Round","require_number":"2","campaign_commence_time":1725670800,"reward_list":[{"reward_type":"4","reward_detail_id":"90001","reward_number":"1"}],"order_id":"5","total_count":"0","is_achieved":false},{"mission_name":"Clear all Forestcraft, Shadowcraft and Bloodcraft puzzles in the Special Round","require_number":"3","campaign_commence_time":1722646800,"reward_list":[{"reward_type":"4","reward_detail_id":"90001","reward_number":"1"}],"order_id":"4","total_count":"0","is_achieved":false},{"mission_name":"Clear all Swordcraft, Runecraft and Havencraft puzzles in the Special Round","require_number":"3","campaign_commence_time":1720227600,"reward_list":[{"reward_type":"4","reward_detail_id":"90001","reward_number":"1"}],"order_id":"3","total_count":"0","is_achieved":false},{"mission_name":"Clear all Special Round puzzles","require_number":"8","campaign_commence_time":1720227600,"reward_list":[{"reward_type":"7","reward_detail_id":"400004315","reward_number":"1"}],"order_id":"2","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 15 puzzles","require_number":"3","campaign_commence_time":1716598800,"reward_list":[{"reward_type":"7","reward_detail_id":"400004314","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 14 puzzles","require_number":"3","campaign_commence_time":1711760400,"reward_list":[{"reward_type":"6","reward_detail_id":"3065004","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 13 puzzles","require_number":"3","campaign_commence_time":1708736400,"reward_list":[{"reward_type":"7","reward_detail_id":"400004313","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 12 puzzles","require_number":"3","campaign_commence_time":1703898000,"reward_list":[{"reward_type":"6","reward_detail_id":"3074009","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 11 puzzles","require_number":"3","campaign_commence_time":1700269200,"reward_list":[{"reward_type":"6","reward_detail_id":"3074008","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 10 puzzles","require_number":"3","campaign_commence_time":1692406800,"reward_list":[{"reward_type":"6","reward_detail_id":"3074007","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 9 puzzles","require_number":"3","campaign_commence_time":1688173200,"reward_list":[{"reward_type":"6","reward_detail_id":"3074006","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 8 puzzles","require_number":"3","campaign_commence_time":1684544400,"reward_list":[{"reward_type":"6","reward_detail_id":"3074005","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 7 puzzles","require_number":"3","campaign_commence_time":1677286800,"reward_list":[{"reward_type":"6","reward_detail_id":"3074004","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 6 puzzles","require_number":"3","campaign_commence_time":1672448400,"reward_list":[{"reward_type":"6","reward_detail_id":"3074003","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 5 puzzles","require_number":"3","campaign_commence_time":1669424400,"reward_list":[{"reward_type":"6","reward_detail_id":"3074002","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 4 puzzles","require_number":"3","campaign_commence_time":1660959000,"reward_list":[{"reward_type":"6","reward_detail_id":"3074001","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 3 puzzles","require_number":"3","campaign_commence_time":1656725400,"reward_list":[{"reward_type":"7","reward_detail_id":"400004105","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 2 puzzles","require_number":"3","campaign_commence_time":1653096600,"reward_list":[{"reward_type":"7","reward_detail_id":"400004104","reward_number":"1"}],"order_id":"1","total_count":"0","is_achieved":false},{"mission_name":"Clear all Round 1 puzzles","require_number":"3","campaign_commence_time":1651282200,"reward_list":[{"reward_type":"10","reward_detail_id":"3704","reward_number":"1"}],"order_id":"1","total_count":"3","is_achieved":true}]}

File diff suppressed because one or more lines are too long

View File

@@ -1,484 +0,0 @@
{
"data_headers": {
"sid": "ac631c29b5f5d07ed5fb6712ad8623c31779553958",
"short_udid": 411054851,
"viewer_id": 906243102,
"servertime": 1779553958,
"result_code": 1
},
"data": {
"user_deck_rotation": [],
"user_deck_unlimited": [],
"maintenance_card_list": [],
"user_deck_my_rotation": [],
"trial_deck_list": [],
"default_deck_list": {
"91": {
"null": 1,
"deck_no": 91,
"class_id": 1,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100111010,
100111010,
100111010,
100011020,
100011020,
100011020,
100012010,
100111020,
100111020,
100111020,
100111040,
100111040,
100111040,
100114010,
100114010,
100114010,
100011030,
100011030,
100011030,
100111060,
100111060,
100111060,
100011040,
100011040,
100011040,
100111030,
100111030,
100111030,
100111050,
100111050,
100111050,
100011050,
100011050,
100011050,
100111070,
100111070,
100111070,
100121010,
100121010,
100121010
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"92": {
"null": 1,
"deck_no": 92,
"class_id": 2,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100211010,
100211010,
100211010,
100011020,
100011020,
100011020,
100012010,
100211020,
100211020,
100211020,
100211060,
100211060,
100211060,
100214010,
100214010,
100214010,
100011030,
100011030,
100011030,
100211030,
100211030,
100211030,
100214020,
100214020,
100214020,
100011040,
100011040,
100011040,
100211040,
100211040,
100211040,
100011050,
100011050,
100011050,
100211050,
100211050,
100211050,
100221020,
100221020,
100221020
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"93": {
"null": 1,
"deck_no": 93,
"class_id": 3,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100314010,
100314010,
100314010,
100011020,
100011020,
100011020,
100012010,
100311010,
100311010,
100311010,
100314030,
100314030,
100314030,
100314020,
100314020,
100314020,
100314040,
100314040,
100314040,
100011030,
100011030,
100011030,
100314050,
100314050,
100314050,
100011040,
100011040,
100011040,
100314060,
100314060,
100314060,
100011050,
100011050,
100011050,
100314070,
100314070,
100314070,
100321010,
100321010,
100321010
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"94": {
"null": 1,
"deck_no": 94,
"class_id": 4,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100414020,
100414020,
100414020,
100011020,
100011020,
100011020,
100012010,
100411010,
100411010,
100411010,
100414010,
100414010,
100414010,
100011030,
100011030,
100011030,
100411050,
100411050,
100411050,
100011040,
100011040,
100011040,
100411030,
100411030,
100411030,
100414030,
100414030,
100414030,
100011050,
100011050,
100011050,
100411020,
100411020,
100411020,
100411040,
100411040,
100411040,
100421020,
100421020,
100421020
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"95": {
"null": 1,
"deck_no": 95,
"class_id": 5,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100011020,
100011020,
100011020,
100012010,
100511010,
100511010,
100511010,
100511020,
100511020,
100511020,
100514010,
100514010,
100514010,
100011030,
100011030,
100011030,
100511030,
100511030,
100511030,
100011040,
100011040,
100011040,
100511040,
100511040,
100511040,
100011050,
100011050,
100011050,
100511050,
100511050,
100511050,
100514020,
100514020,
100514020,
100511060,
100511060,
100511060,
100521030,
100521030,
100521030
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"96": {
"null": 1,
"deck_no": 96,
"class_id": 6,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100011020,
100011020,
100011020,
100012010,
100611010,
100611010,
100611010,
100611020,
100611020,
100611020,
100614010,
100614010,
100614010,
100614020,
100614020,
100614020,
100011030,
100011030,
100011030,
100611030,
100611030,
100611030,
100011040,
100011040,
100011040,
100611050,
100611050,
100611050,
100614030,
100614030,
100614030,
100011050,
100011050,
100011050,
100611040,
100611040,
100611040,
100621010,
100621010,
100621010
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"97": {
"null": 1,
"deck_no": 97,
"class_id": 7,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100713020,
100713020,
100713020,
100011020,
100011020,
100011020,
100012010,
100713010,
100713010,
100713010,
100711010,
100711010,
100711010,
100714010,
100714010,
100714010,
100714020,
100714020,
100714020,
100011030,
100011030,
100011030,
100713030,
100713030,
100713030,
100011040,
100011040,
100011040,
100011050,
100011050,
100011050,
100723010,
100723010,
100723010,
100714030,
100714030,
100714030,
100711020,
100711020,
100711020
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
},
"98": {
"null": 1,
"deck_no": 98,
"class_id": 8,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100011020,
100011020,
100011020,
100012010,
100811020,
100811020,
100811020,
100811060,
100811060,
100811060,
100811070,
100811070,
100811070,
100814010,
100814010,
100814010,
100011030,
100011030,
100011030,
100811010,
100811010,
100811010,
100811030,
100811030,
100811030,
100011040,
100011040,
100011040,
100811040,
100811040,
100811040,
100824010,
100824010,
100824010,
100011050,
100011050,
100011050,
100811050,
100811050,
100811050
],
"is_complete_deck": 1,
"is_available_deck": 1,
"maintenance_card_ids": []
}
},
"user_leader_skin_setting_list": {
"1": {
"class_id": 1,
"is_random_leader_skin": 0,
"leader_skin_id": 1
},
"2": {
"class_id": 2,
"is_random_leader_skin": 0,
"leader_skin_id": 2
},
"3": {
"class_id": 3,
"is_random_leader_skin": 0,
"leader_skin_id": 3
},
"4": {
"class_id": 4,
"is_random_leader_skin": 0,
"leader_skin_id": 4
},
"5": {
"class_id": 5,
"is_random_leader_skin": 0,
"leader_skin_id": 5
},
"6": {
"class_id": 6,
"is_random_leader_skin": 0,
"leader_skin_id": 6
},
"7": {
"class_id": 7,
"is_random_leader_skin": 0,
"leader_skin_id": 7
},
"8": {
"class_id": 8,
"is_random_leader_skin": 0,
"leader_skin_id": 8
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,229 +0,0 @@
{
"data_headers": {
"short_udid": 411054851,
"viewer_id": 906243102,
"sid": "",
"servertime": 1779553959,
"result_code": 1
},
"data": {
"user_info": {
"device_type": "2",
"name": "combusty7",
"country_code": "KOR",
"max_friend": "20",
"last_play_time": "2026-05-23 16:32:39",
"is_received_two_pick_mission": "1",
"birth": "19600101",
"selected_emblem_id": "701441011",
"selected_degree_id": "300003",
"mission_change_time": "2017-09-17 14:47:13",
"mission_receive_type": "0",
"is_official": "0",
"is_official_mark_displayed": "0"
},
"sealed_info": {
"enable": 1,
"crystal_cost": 600,
"rupy_cost": 600,
"ticket_cost": 4,
"is_join": false,
"pack_info": [
10032,
10032,
10031,
10030,
10029
],
"deck_using_num_min": 30,
"schedule_id": 21,
"is_deck_code_maintenance": false,
"sales_period_info": {
"sales_period_series": 33
}
},
"colosseum_info": {
"colosseum_id": "165",
"is_display_tips": "0",
"tips_id": "0",
"card_pool_name": "Take Two (DragonbladeRivenbrandt)",
"is_colosseum_period": true,
"is_round_period": true,
"deck_format": "3",
"is_normal_two_pick": "1",
"is_special_mode": "10",
"is_all_card_enabled": 0,
"start_time": "2026-05-21 06:00:00",
"colosseum_name": "Rivenbrandt Take Two Cup",
"now_round": "1",
"end_time": "2026-05-25 19:59:59",
"sales_period_info": {
"sales_period_time": "2026-05-25 19:59:59"
}
},
"is_available_colosseum_free_entry": true,
"arena_info": [
{
"mode": 1,
"enable": 1,
"cost": 150,
"rupy_cost": 150,
"ticket_cost": 1,
"is_join": false,
"format_info": {
"two_pick_type": "1",
"card_pool_name": "Take Two (DragonbladeRivenbrandt)",
"announce_id": 0,
"last_card_pack_set_id": "10029",
"start_time": "2026-05-01 02:00:00",
"end_time": "2026-06-01 01:59:59"
},
"sales_period_info": {
"sales_period_time": "2026-06-01 01:59:59"
}
}
],
"is_arena_challenge_period": true,
"is_hidden_boss_appeared": false,
"competition_info": {
"is_competition_period": false
},
"treasure_info": null,
"unread_present_count": 0,
"unreceived_mission_reward_count": 0,
"lottery_period_info": null,
"master_point_ranking_period": {
"id": "119",
"period_num": "118",
"necessary_score": "0",
"begin_time": "2026-05-01 02:00:00",
"end_time": "2026-06-01 01:59:59"
},
"last_announce_id": "3353",
"last_announce_update_time": "2026-05-15 10:22:11",
"unfinished_battle_exists": false,
"is_joined_room": false,
"receive_friend_apply_count": 0,
"feature_maintenance_list": [],
"can_give_daily_login_bonus": false,
"friend_battle_invite_count": 0,
"user_config": {
"receive_invitation": "1",
"receive_invitation_in_battle": "1",
"receive_invitation_in_offline": "1",
"receive_friend_apply": "1",
"is_allow_send_adjust": "1",
"is_foil_preferred": "0",
"is_prize_preferred": "0"
},
"banner": [
{
"image_name": "banner_000788",
"click": "account_transition_with_two",
"status": "10",
"change_time": "10",
"remaining_time": "0",
"image_paths": []
},
{
"image_name": "banner_000906",
"click": "colosseum",
"status": "",
"change_time": "10",
"remaining_time": "0",
"image_paths": []
},
{
"image_name": "banner_000220",
"click": "deck_intro_rotation",
"status": "17",
"change_time": "10",
"remaining_time": "0",
"image_paths": []
},
{
"image_name": "banner_000840",
"click": "mission",
"status": "2",
"change_time": "10",
"remaining_time": "0",
"image_paths": []
}
],
"sub_banner": null,
"sub_banner_list": [],
"user_mypage_info": {
"user_mypage_setting": {
"mypage_id": "0",
"select_type": "0",
"mypage_id_list": []
}
},
"user_offline_event": [],
"convention": {
"is_join_tournament": false,
"recent_start_date": null,
"is_admin_watch_user": false
},
"special_crystal_info": [],
"room_type_in_session": {
"special_deck_format_list": [
{
"deck_format": "5",
"end_time": "2030-06-26 19:59:59"
}
]
},
"guild_notification": {
"guild_id": null,
"guild_room_message_id": null,
"is_join_request": false,
"is_invited": false
},
"shop_notification": {
"card_pack": {
"is_open_free_gacha_campaign": false,
"can_free_gacha": false
},
"build_deck": [],
"sleeve": [],
"leader_skin": []
},
"pre_release_status": 0,
"gathering_info": {
"has_invite": 0,
"is_entry": 0
},
"quest": {
"is_open": false,
"is_display_badge": false,
"is_daily_first_access": false,
"end_time": "",
"name": ""
},
"basic_puzzle": {
"is_display_badge": true
},
"all_card_enabled_period": null,
"user_item_list": [
{
"item_id": "1",
"number": "19"
},
{
"item_id": "10011",
"number": "1"
},
{
"item_id": "80001",
"number": "1"
}
],
"is_battle_pass_period": true,
"story_notification": {
"is_display_ribbon": false,
"is_display_badge": false
},
"home_dialog_list": []
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,201 +0,0 @@
{
"data_headers": {
"sid": "ac631c29b5f5d07ed5fb6712ad8623c31779553960",
"short_udid": 411054851,
"viewer_id": 906243102,
"servertime": 1779553960,
"result_code": 1
},
"data": {
"10011": {
"record_id": "21",
"id": "8",
"store_product_id": "10011",
"name": "60-crystal set",
"text": "Purchase 60 Crystals",
"price": "0.99",
"charge_crystal_num": "60",
"free_crystal_num": "0",
"purchase_limit": "999999999",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal",
"start_time": "2022-10-05 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"30011": {
"record_id": "26",
"id": "10",
"store_product_id": "30011",
"name": "670-crystal set",
"text": "Purchase 670 Crystals",
"price": "10.99",
"charge_crystal_num": "670",
"free_crystal_num": "0",
"purchase_limit": "999999999",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal",
"start_time": "2022-10-05 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"40000": {
"record_id": "27",
"id": "4",
"store_product_id": "40000",
"name": "1200-crystal set",
"text": "Purchase 1200 Crystals",
"price": "20.99",
"charge_crystal_num": "1200",
"free_crystal_num": "0",
"purchase_limit": "999999999",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal",
"start_time": "2015-03-01 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"50000": {
"record_id": "28",
"id": "5",
"store_product_id": "50000",
"name": "2400-crystal set",
"text": "Purchase 2400 Crystals",
"price": "39.99",
"charge_crystal_num": "2400",
"free_crystal_num": "0",
"purchase_limit": "999999999",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal",
"start_time": "2015-03-01 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"60000": {
"record_id": "29",
"id": "6",
"store_product_id": "60000",
"name": "5000-crystal set",
"text": "Purchase 5000 Crystals",
"price": "79.99",
"charge_crystal_num": "5000",
"free_crystal_num": "0",
"purchase_limit": "999999999",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal",
"start_time": "2015-03-01 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"70011": {
"record_id": "24",
"id": "9",
"store_product_id": "70011",
"name": "350-crystal set",
"text": "Purchase 350 Crystals",
"price": "5.99",
"charge_crystal_num": "350",
"free_crystal_num": "0",
"purchase_limit": "999999999",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal",
"start_time": "2022-10-05 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"80000": {
"record_id": "30",
"id": "800",
"store_product_id": "80000",
"name": "1200-crystal and Legendary set",
"text": "Purchase 1200 Crystals and Legendary set",
"price": "20.99",
"charge_crystal_num": "1200",
"free_crystal_num": "0",
"purchase_limit": "3",
"special_shop_flag": "1",
"image_name": "thumbnail_crystal_strong",
"start_time": "2018-01-01 00:00:00",
"end_time": "2019-03-19 16:15:17",
"remaining_time": "604800",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"98900": {
"record_id": "19",
"id": "989",
"store_product_id": "98900",
"name": "[b]1-Time Deal![/b] 1000-crystal set",
"text": "Purchase 1000 Crystals",
"price": "15.99",
"charge_crystal_num": "1000",
"free_crystal_num": "0",
"purchase_limit": "1",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal_strong",
"start_time": "2026-04-01 02:00:00",
"end_time": "2026-07-01 01:59:59",
"remaining_time": "0",
"is_resale_product": "1",
"resale_start_date": "2026-04-01 02:00:00",
"purchase_num_current": 0
},
"99200": {
"record_id": "3",
"id": "992",
"store_product_id": "99200",
"name": "[b]One-time Deal![/b] 800-crystal set",
"text": "Purchase 800 Crystals",
"price": "7.99",
"charge_crystal_num": "800",
"free_crystal_num": "0",
"purchase_limit": "1",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal_strong",
"start_time": "2018-01-30 04:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 0
},
"99400": {
"record_id": "10",
"id": "994",
"store_product_id": "99400",
"name": "[b]Special Offer![/b] 7500-crystal set (3 times per person)",
"text": "Purchase 7500 Crystals",
"price": "79.99",
"charge_crystal_num": "7500",
"free_crystal_num": "0",
"purchase_limit": "3",
"special_shop_flag": "0",
"image_name": "thumbnail_crystal_strong",
"start_time": "2017-06-01 06:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": "0",
"is_resale_product": "0",
"resale_start_date": "",
"purchase_num_current": 2
}
}
}

File diff suppressed because one or more lines are too long

View File

View File

@@ -0,0 +1,17 @@
{
"id": 1,
"mode": 1,
"enable": 1,
"cost": 150,
"rupy_cost": 150,
"ticket_cost": 1,
"is_join": false,
"format_info": {
"two_pick_type": "1",
"card_pool_name": "Take Two (DragonbladeRivenbrandt)",
"announce_id": 0,
"last_card_pack_set_id": "10029",
"start_time": "2026-05-01 02:00:00",
"end_time": "2026-06-01 01:59:59"
}
}

View File

@@ -0,0 +1,266 @@
[
{
"id": 2801,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "-1|-7",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930144051:930144061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.play_count}>=2&{me.play_count}<4)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.play_count}>=4)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930144051:930144061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.play_count}>=2&{me.play_count}<4)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.play_count}>=4)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"ability_desc": "Subtract 1 from the cost of a Natura card in your hand.|Give all allied followers [b]Storm[/b] and the ability to attack 2 times per turn.",
"passive_ability_desc": "At the start of the match, if you are going second, gain 1 valor.\\nAt the end of your turn, if you've played at least 2 cards this turn, gain 1 valor. If you've played at least 4, gain 2 valor instead."
},
{
"id": 3301,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-4",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930144011:930144021:930144031)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930144011:930144021:930144031)(preprocess:none)",
"ability_desc": "If there are any [b]Fairies[/b] in your hand, change the cost of a random [b]Fairy[/b] in your hand to 0 until the end of the turn. Otherwise, put a [b]Fairy[/b] into your hand.|Give +1/+0 to an allied follower. Deal X damage to a random enemy follower. X equals the attack of the strongest allied follower in play.|Give +X/+Y to an allied 1-play-point follower. X and Y equal the number of allied Forestcraft followers that have left play this match, split randomly. If X plus Y is at least 20, give that follower [b]Storm[/b].<<{me.inplay.class.count}+1??\\n(Followers that have left play: <<{me.game_left_cards.unit.clan=elf.count}>>/20)>>",
"passive_ability_desc": ""
},
{
"id": 4101,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "0|0|-8",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930144071:930144081:930144091)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.turn_accelerate_count=me:0}>0&{me.inplay.class.turn_accelerate_count=me:0}<2)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.turn_accelerate_count=me:0}>1)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930144071:930144081:930144091)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.turn_accelerate_count=me:0}>0&{me.inplay.class.turn_accelerate_count=me:0}<2)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.turn_accelerate_count=me:0}>1)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"ability_desc": "Put a random follower with [b]Accelerate[/b] from your deck into your hand.|Give your leader the following effect until the end of the turn: Whenever you play a card using [b]Accelerate[/b], deal 1 damage to a random enemy follower.|Put a [b]Purgation's Blessing[/b] into your hand.\\nGive your leader the following effect: At the start of your every other turn, put a [b]Purgation's Blessing[/b] into your hand. (This effect lasts for the rest of the match.)",
"passive_ability_desc": "At the start of the match, if you are going second, gain 1 valor.\\nAt the end of your turn, if you've played a card using [b]Accelerate[/b] this turn, gain 1 valor. If you've played at least 2, gain 2 valor instead.<<{me.inplay.class.count}+1??\\n(Times [b]Accelerate[/b][b]d[/b]: <<{me.inplay.class.turn_accelerate_count_text=me:0}>>)>>"
},
{
"id": 2602,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|-2|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930244041:930244051:930244061)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930244041:930244051:930244061)(preprocess:none)",
"ability_desc": "Give +1/+0 to a random allied follower.\\nAt the start of your next turn, draw a card.|Summon an [b]Arcane Personnel Carrier[/b].\\n[b]Rally[/b] [b](10)[/b]: Summon a [b]Val, Speedy Magicar[/b].<<{me.inplay.class.count}+1??\\n([b]Rally[/b] count: <<{me.inplay.class.rally_count}>>/10)>>|Deal 3 damage to a random enemy. Do this 2 times.",
"passive_ability_desc": ""
},
{
"id": 3302,
"battle_start_first_player_turn_bp": 4,
"battle_start_second_player_turn_bp": 4,
"battle_start_max_life": 25,
"ability_cost": "+1|0|-6",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930244071:930244081:930244091)(preprocess:none),(skill:change_affiliation)(timing:self_turn_start)(condition:{me.inplay.class.turn}=1)(target:{me.hand.unit})(option:tribe=legion&type=add)(preprocess:none),(skill:change_affiliation)(timing:self_turn_start)(condition:{me.inplay.class.turn}=1)(target:{me.deck.unit})(option:tribe=legion&type=add)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930244071:930244081:930244091)(preprocess:none),(skill:change_affiliation)(timing:self_turn_start)(condition:{me.inplay.class.turn}=1)(target:{me.hand.unit})(option:tribe=legion&type=add)(preprocess:none),(skill:change_affiliation)(timing:self_turn_start)(condition:{me.inplay.class.turn}=1)(target:{me.deck.unit})(option:tribe=legion&type=add)(preprocess:none)",
"ability_desc": "Discard a follower from your hand and put a copy of it into your deck. Give that copy the Officer trait.\\nDraw a card.|At the end of your turn, deal 2 damage to a random enemy follower. Before damage is dealt, if there are fewer allied followers than enemy followers in play, gain 1 valor.|Give your leader the following effect: Once on each of your turns, when you play a follower, put a random X-play-point follower from your deck into play. X equals the original cost of the follower you played. (This effect lasts for the rest of the match.)",
"passive_ability_desc": "At the start of your turn, if this is your first turn, give the Officer trait to all followers in your hand and deck."
},
{
"id": 3502,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|-2|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930244011:930244021:930244031)(preprocess:none),(skill:change_rally_count)(timing:when_play_other)(condition:{me.inplay_self.count}>0&character=me&target=played_card&card_type=spell&base_card_id=900214050)(target:character=me&target=inplay&card_type=class)(option:add_rally_count=1)(preprocess:per_turn=1),(skill:pp_modifier)(timing:when_play_other)(condition:{me.inplay_self.count}>0&character=me&target=played_card&card_type=spell&base_card_id=900214050)(target:character=me&target=inplay&card_type=class)(option:add_pp=1)(preprocess:per_turn=1)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930244011:930244021:930244031)(preprocess:none),(skill:change_rally_count)(timing:when_play_other)(condition:{me.inplay_self.count}>0&character=me&target=played_card&card_type=spell&base_card_id=900214050)(target:character=me&target=inplay&card_type=class)(option:add_rally_count=1)(preprocess:per_turn=1),(skill:pp_modifier)(timing:when_play_other)(condition:{me.inplay_self.count}>0&character=me&target=played_card&card_type=spell&base_card_id=900214050)(target:character=me&target=inplay&card_type=class)(option:add_pp=1)(preprocess:per_turn=1)",
"ability_desc": "Discard a card from your hand.\\nPut a random Festive card from your deck into your hand.|Give your leader the following effect: The next time your leader takes damage, reduce that damage to 0.|Give your leader the following effect until the end of the turn: Whenever you play a [b]Glittering Gold[/b], give +1/+0 to all allied followers and draw a card.",
"passive_ability_desc": "Once on each of your turns, when you play a [b]Glittering Gold[/b], add 1 to your [b]Rally[/b] count and recover 1 play point."
},
{
"id": 3903,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|0|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930344071:930344081:930344091)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930344071:930344081:930344091)(preprocess:none)",
"ability_desc": "Select a card in your hand. If it's been [b]Spellboost[/b][b]ed[/b] less than 5 times, [b]Spellboost[/b] it. Otherwise, put a copy of that card into your deck and subtract 1 from the copy's cost.|Discard a card with [b]Spellboost[/b] from your hand. Put a random card with [b]Spellboost[/b] from your deck into your hand and [b]Spellboost[/b] it X times. X equals the number of times the discarded card had been [b]Spellboost[/b][b]ed[/b].|Select a card with [b]Spellboost[/b] in your hand. Put a copy of it into your hand and [b]Spellboost[/b] the copy X times. X equals the number of times the selected card has been [b]Spellboost[/b][b]ed[/b].",
"passive_ability_desc": ""
},
{
"id": 2523,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "0|-1|-8",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930344011:930344021:930344031)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards.all.play_moment_tribe=machine.count}>0&{me.turn_play_cards.all.play_moment_tribe=machine.count}<2)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards.all.play_moment_tribe=machine.count}>=2)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930344011:930344021:930344031)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards.all.play_moment_tribe=machine.count}>0&{me.turn_play_cards.all.play_moment_tribe=machine.count}<2)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards.all.play_moment_tribe=machine.count}>=2)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"ability_desc": "Put a [b]Recovery[/b] into your hand.|Until the end of the turn, subtract 1 from the cost of a Machina card in your hand.|Discard 2 cards from your hand.\\nPut a [b]Ladica, Verdant Claw[/b] and [b]Azure Blast[/b] into your hand.",
"passive_ability_desc": "At the start of the match, if you are going second, gain 1 valor.\\nAt the end of your turn, if you've played at least 1 Machina card this turn, gain 1 valor. If you've played at least 2, gain 2 valor instead.<<{me.inplay.class.count}+1??\\n(Machina cards played: <<{me.turn_play_cards_other_self.all.play_moment_tribe=machine.count}>>)>>"
},
{
"id": 4103,
"battle_start_first_player_turn_bp": 2,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930344041:930344051:930344061)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930344041:930344051:930344061)(preprocess:none)",
"ability_desc": "Put 1 of the following cards into your hand in order and change its cost to 0.\\n<<${me.inplay.class.count}>0&{me.game_skill_activated.base_card_id=930344040.count}%2=0?[9acd32]1.[-]?1.>> [b]Insight[/b]\\n<<${me.inplay.class.count}>0&{me.game_skill_activated.base_card_id=930344040.count}%2=1?[9acd32]2.[-]?2.>> [b]Angelic Snipe[/b]|Give an unevolved allied follower the following effect and evolve it: At the end of your turn, banish this follower. ([b]Evolve[/b] effects will not activate.)|Change the cost, attack, and defense of a follower in your hand to 1.",
"passive_ability_desc": ""
},
{
"id": 2804,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "-1|-1|-6",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930444041:930444051:930444061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.destroyed_this_turn_card_list.all.base_card_id=900012050.count}>0)(target:character=me&target=inplay&card_type=class)(option:add_bp={me.destroyed_this_turn_card_list.all.base_card_id=900012050.count})(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930444041:930444051:930444061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.destroyed_this_turn_card_list.all.base_card_id=900012050.count}>0)(target:character=me&target=inplay&card_type=class)(option:add_bp={me.destroyed_this_turn_card_list.all.base_card_id=900012050.count})(preprocess:none)",
"ability_desc": "Give an allied Natura follower +1/+0 and [b]Rush[/b].|Subtract 1 from the cost of a Natura card in your hand until the end of the turn.|Give your leader the following effect: Once on each of your turns, when an allied [b]Naterran World Tree[/b] is destroyed, if it's the second this turn, summon a [b]Bayleon, Shining Sovereign[/b] and activate its [b]Fanfare[/b] effect. (This effect lasts for the rest of the match.) If your leader already has this effect, give +1/+1 to all allied followers and restore 3 defense to your leader instead.<<{me.inplay.class.count}+1??\\n([b]Naterran World Tree[/b][b]s[/b] destroyed: <<{me.destroyed_this_turn_card_list.all.base_card_id=900012050.count}>>/2)>>",
"passive_ability_desc": "At the start of the match, if you are going second, gain 1 valor.\\nAt the end of your turn, gain X valor. X equals the number of allied [b]Naterran World Tree[/b][b]s[/b] destroyed this turn.<<{me.inplay.class.count}+1??\\n([b]Naterran World Tree[/b][b]s[/b] destroyed: <<{me.destroyed_this_turn_card_list.all.base_card_id=900012050.count}>>)>>"
},
{
"id": 2604,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-5",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930444011:930444021:930444031)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930444011:930444021:930444031)(preprocess:none)",
"ability_desc": "Deal 1 damage to a follower or the enemy leader. If you selected an allied follower, give +0/+2 to that follower.|Select a follower in play. Give it +2/+0 and deal 2 damage to it. If that follower is not destroyed by this effect, gain 1 valor.|Give your leader the following effect: Once on each of your turns after you become able to evolve, when an allied follower takes damage, if it's not destroyed, give it +2/+2 and deal 5 damage to a random enemy follower. (This effect lasts for the rest of the match.)",
"passive_ability_desc": ""
},
{
"id": 3504,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|0|-6",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930444071:930444081:930444091)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930444071:930444081:930444091)(preprocess:none)",
"ability_desc": "At the end of your turn, draw a card.|Discard a card from your hand.\\nDeal 2 damage to a random enemy follower and 1 damage to the enemy leader.|Put a [b]Tidal Surge[/b] into your hand.\\nGive your leader the following effect: At the start of your turn, if there are no [b]Tidal Surge[/b][b]s[/b] in your hand, put 1 into your hand. (This effect lasts for the rest of the match.)",
"passive_ability_desc": ""
},
{
"id": 1605,
"battle_start_first_player_turn_bp": 2,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-4",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930544041:930544051:930544061)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930544041:930544051:930544061)(preprocess:none)",
"ability_desc": "If there are any [b]Assembly Droid[/b][b]s[/b] in your hand, change the cost of a random [b]Assembly Droid[/b] in your hand to 0 until the end of the turn. Otherwise, put an [b]Assembly Droid[/b] into your hand.|Summon a [b]Roly-Poly Mk I, Stalwart Bot[/b] and evolve it.|Discard 2 cards from your hand.\\nPut a [b]Tetra, Sapphire Leader[/b] and [b]Mono, Garnet Challenger[/b] into your hand.",
"passive_ability_desc": ""
},
{
"id": 2605,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|-2|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930544071:930544081:930544091)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930544071:930544081:930544091)(preprocess:none)",
"ability_desc": "Deal 1 damage to a follower or the enemy leader. If you selected an allied follower, before damage is dealt, give it [b]Last Words[/b]: Draw a card.|Activate an allied follower's [b]Last Words[/b] effects.|Give an allied follower the following effects.\\n-[b]Rush[/b].\\n-[b]Strike[/b]: Gain +2/+0.\\n-[b]Follower[/b] [b]Strike[/b]: Deal X damage to the enemy leader. X equals this follower's attack.",
"passive_ability_desc": ""
},
{
"id": 3505,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930544011:930544021:930544031)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930544011:930544021:930544031)(preprocess:none)",
"ability_desc": "Give a random allied follower the following effect: When this follower leaves play, draw a card.|Until the end of the turn, subtract 2 from the cost of a follower in your hand and give it the following effect: If this follower is in play, when it leaves play, or at the end of your turn, banish this follower.|Give an allied follower [b]Rush[/b], [b]Bane[/b], and [b]Last Words[/b]: [b]Necromancy[/b] [b](2)[/b] - Summon a copy of this follower and give it [b]Storm[/b]. If you have at least 10 shadows, perform [b]Necromancy[/b] [b](10)[/b] - Summon 2 copies instead and give them [b]Storm[/b].",
"passive_ability_desc": ""
},
{
"id": 1606,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|-2|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930644041:930644051:930644061)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930644041:930644051:930644061)(preprocess:none)",
"ability_desc": "Deal 1 damage to a random enemy. If [b]Vengeance[/b] is active for you, deal 2 damage instead, and at the start of your next turn, draw a card.|Activate [b]Vengeance[/b] until the end of your next turn, even if your leader's defense is greater than 10.|Give your leader the following effect until the end of the turn: Whenever an unevolved allied follower's attack or defense is increased by an effect, evolve that follower and give it [b]Storm[/b].",
"passive_ability_desc": ""
},
{
"id": 2606,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-2",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930644011:930644021:930644031)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930644011:930644021:930644031)(preprocess:none)",
"ability_desc": "Give +1/+0 to a random allied [b]Forest Bat[/b].\\nDraw a card.|Evolve an allied [b]Forest Bat[/b] and give it [b]Last Words[/b]: Give your leader the following effect - At the end of your opponent's turn, summon a [b]Forest Bat[/b], then remove this effect.|Summon 2 [b]Forest Bat[/b][b]s[/b].\\nDeal X damage to the enemy leader. X equals the number of allied [b]Forest Bat[/b][b]s[/b] in play.",
"passive_ability_desc": ""
},
{
"id": 3506,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 1,
"battle_start_max_life": 25,
"ability_cost": "+1|0|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930644071:930644081:930644091)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930644071:930644081:930644091)(preprocess:none)",
"ability_desc": "Give a follower in you hand [b]Fanfare[/b]: Randomly activate 1 of the following effects.\\n-Deal 1 damage to the enemy leader.\\n-Restore 1 defense to your leader.|At the start of your next turn, draw a card.\\nIf it's a turn you are able to evolve, you have at least 1 valor, and you have 0 evolution points, use 1 valor and recover 1 evolution point.|Give your leader the following effect until the end of the turn: Whenever an allied follower evolves, recover 1 play point and deal 1 damage to the enemy leader.",
"passive_ability_desc": ""
},
{
"id": 2507,
"battle_start_first_player_turn_bp": 1,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "-1|-5",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930744051:930744061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}>0&{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}<2)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}>=2)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930744051:930744061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}>0&{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}<2)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}>=2)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none)",
"ability_desc": "Put copies of 2 different allied Natura cards fused this match into your hand.|Give your leader the following effect: At the end of your turn, deal X damage to a random enemy follower. If no enemy followers are in play, deal X damage to the enemy leader. X equals half the number of allied Natura cards fused this match (rounded up). (This effect lasts for the rest of the match.)<<{me.inplay.class.count}+1??\\n(X equals: <<{me.game_fusion_ingrediented_cards.all.tribe=nature.count.half_round_up}>>)>>",
"passive_ability_desc": "At the start of the match, if you are going second, gain 1 valor.\\nAt the end of your turn, if you've fused or played a Natura card this turn, gain 1 valor. If you've fused or played at least 2, gain 2 valor instead.<<${me.inplay.class.is_turn=self}=1&{me.inplay.class.count}>0?\\n(Natura cards fused or played: <<{me.turn_play_cards_other_self.all.play_moment_tribe=nature.count}+{me.fusion_ingrediented_this_turn_card_list.all.tribe=nature.count}>>)?>><<${me.inplay.class.is_turn=self}=0&{me.inplay.class.count}>0?\\n(Natura cards fused or played: 0)?>>"
},
{
"id": 2607,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|0|-6",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930744071:930744081:930744091)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930744071:930744081:930744091)(preprocess:none)",
"ability_desc": "Deal 1 damage to an enemy.|Subtract 1 from the [b]Countdown[/b] of an allied amulet. Then, if that amulet's [b]Countdown[/b] reached 0, gain 1 valor.|Subtract 2 from the [b]Countdown[/b] of all allied amulets and gain X valor. X equals the number of amulets destroyed by this effect. If X is at least 4, put a [b]Fiery Reproach[/b] into your hand.",
"passive_ability_desc": ""
},
{
"id": 4107,
"battle_start_first_player_turn_bp": 0,
"battle_start_second_player_turn_bp": 0,
"battle_start_max_life": 25,
"ability_cost": "0|0|-9",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930744011:930744021:930744031)(preprocess:none),(skill:bp_modifier)(timing:when_evolve_other)(condition:{me.evolution_card.unit.count}>0&turn=self)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930744011:930744021:930744031)(preprocess:none),(skill:bp_modifier)(timing:when_evolve_other)(condition:{me.evolution_card.unit.count}>0&turn=self)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none)",
"ability_desc": "Give +0/+1 to an allied follower. If it's an evolved follower, give it +0/+2 instead.|Discard a card from your hand and put a copy of it into your deck.\\nDraw a card.|Recover all of your evolution points. \\nDraw cards until there are 7 cards in your hand and subtract 3 from the costs of all cards in your hand.",
"passive_ability_desc": "During your turn, whenever an allied follower evolves, gain 1 valor."
},
{
"id": 508,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|-1|-5",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930844071:930844081:930844091)(preprocess:none),(skill:summon_token)(timing:self_turn_start)(condition:{me.inplay.field.base_card_id=122033010.count}=0&{me.hand.field.base_card_id=122033010.count}=0)(target:none)(option:summon_token=122033011)(preprocess:none),(skill:chant_count_change)(timing:self_turn_start)(condition:{me.inplay.field.base_card_id=122033010.count}=0&{me.hand.field.base_card_id=122033010.count}=0)(target:character=me&target=skill_summoned_card&card_type=chant_field)(option:add_chant=10)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930844071:930844081:930844091)(preprocess:none),(skill:summon_token)(timing:self_turn_start)(condition:{me.inplay.field.base_card_id=122033010.count}=0&{me.hand.field.base_card_id=122033010.count}=0)(target:none)(option:summon_token=122033011)(preprocess:none),(skill:chant_count_change)(timing:self_turn_start)(condition:{me.inplay.field.base_card_id=122033010.count}=0&{me.hand.field.base_card_id=122033010.count}=0)(target:character=me&target=skill_summoned_card&card_type=chant_field)(option:add_chant=10)(preprocess:none)",
"ability_desc": "Deal 1 damage to a follower or the enemy leader. If you selected an allied [b]Shadow Soldier[/b], before damage is dealt, give it +1/+1.|Destroy an allied follower that cost 5 or less play points. Give a random allied follower +X/+X. X equals the cost of the destroyed follower.|Give your leader the following effect: Whenever an allied [b]Shadow Soldier[/b], [b]Shadow Commander[/b], [b]Shadow Berserker[/b], or [b]Shadow General[/b] comes into play, give it [b]Storm[/b]. (This effect lasts for the rest of the match.)",
"passive_ability_desc": "At the start of your turn, if there aren't any allied [b]Brand of the Morning Star[/b][b]s[/b] in your hand or in play, summon 1 and add 10 to its [b]Countdown[/b]."
},
{
"id": 3908,
"battle_start_first_player_turn_bp": 2,
"battle_start_second_player_turn_bp": 2,
"battle_start_max_life": 25,
"ability_cost": "+1|0|-X",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930844041:930844051:930844061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.pp}>=1&{me.inplay.class.pp}<3)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.pp}>=3)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none),(skill:draw)(timing:self_turn_end)(condition:{me.inplay.class.pp}>=3)(target:character=me&target=deck&card_type=all&random_count=1)(option:none)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930844041:930844051:930844061)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.pp}>=1&{me.inplay.class.pp}<3)(target:character=me&target=inplay&card_type=class)(option:add_bp=1)(preprocess:none),(skill:bp_modifier)(timing:self_turn_end)(condition:{me.inplay.class.pp}>=3)(target:character=me&target=inplay&card_type=class)(option:add_bp=2)(preprocess:none),(skill:draw)(timing:self_turn_end)(condition:{me.inplay.class.pp}>=3)(target:character=me&target=deck&card_type=all&random_count=1)(option:none)(preprocess:none)",
"ability_desc": "Deal 1 damage to an enemy.|Recover 1 play point.|Give +X/+0 to an allied follower. If X is at least 10, give it [b]Storm[/b]. X equals the amount of valor you have.<<{me.inplay.class.count}+1??\\n(X equals: <<{me.inplay.class.bp}>>/10)>>",
"passive_ability_desc": "At the end of your turn, if you have at least 1 play point, gain 1 valor. If you have at least 3 play points, gain 2 valor and draw a card instead."
},
{
"id": 2518,
"battle_start_first_player_turn_bp": 3,
"battle_start_second_player_turn_bp": 3,
"battle_start_max_life": 25,
"ability_cost": "+1|-2|-3",
"ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930844011:930844021:930844031)(preprocess:none)",
"passive_ability": "(skill:choice)(timing:when_choice_brave)(condition:character=me)(target:none)(option:card_id=930844011:930844021:930844031)(preprocess:none)",
"ability_desc": "If [b]Resonance[/b] is active for you, subtract 1 from the cost of a random [b]Paradigm Shift[/b] in your hand. Otherwise, deal 1 damage to a random enemy follower and then the enemy leader.|Summon an [b]Acceleratium[/b].|Give your leader the following effect until the end of the turn: Whenever an allied Artifact card comes into play, recover 1 play point and draw a card.",
"passive_ability_desc": ""
}
]

View File

@@ -0,0 +1,38 @@
[
{
"id": 1,
"image_name": "banner_000788",
"click": "account_transition_with_two",
"status": "10",
"change_time": 10,
"remaining_time": 0,
"image_paths": []
},
{
"id": 2,
"image_name": "banner_000906",
"click": "colosseum",
"status": "",
"change_time": 10,
"remaining_time": 0,
"image_paths": []
},
{
"id": 3,
"image_name": "banner_000220",
"click": "deck_intro_rotation",
"status": "17",
"change_time": 10,
"remaining_time": 0,
"image_paths": []
},
{
"id": 4,
"image_name": "banner_000840",
"click": "mission",
"status": "2",
"change_time": 10,
"remaining_time": 0,
"image_paths": []
}
]

View File

@@ -0,0 +1,402 @@
[
{
"level": 1,
"required_point": 0
},
{
"level": 2,
"required_point": 500
},
{
"level": 3,
"required_point": 1000
},
{
"level": 4,
"required_point": 1500
},
{
"level": 5,
"required_point": 2000
},
{
"level": 6,
"required_point": 2500
},
{
"level": 7,
"required_point": 3000
},
{
"level": 8,
"required_point": 3500
},
{
"level": 9,
"required_point": 4000
},
{
"level": 10,
"required_point": 4500
},
{
"level": 11,
"required_point": 5000
},
{
"level": 12,
"required_point": 5500
},
{
"level": 13,
"required_point": 6000
},
{
"level": 14,
"required_point": 6500
},
{
"level": 15,
"required_point": 7000
},
{
"level": 16,
"required_point": 7500
},
{
"level": 17,
"required_point": 8000
},
{
"level": 18,
"required_point": 8500
},
{
"level": 19,
"required_point": 9000
},
{
"level": 20,
"required_point": 9500
},
{
"level": 21,
"required_point": 10000
},
{
"level": 22,
"required_point": 10500
},
{
"level": 23,
"required_point": 11000
},
{
"level": 24,
"required_point": 11500
},
{
"level": 25,
"required_point": 12000
},
{
"level": 26,
"required_point": 12500
},
{
"level": 27,
"required_point": 13000
},
{
"level": 28,
"required_point": 13500
},
{
"level": 29,
"required_point": 14000
},
{
"level": 30,
"required_point": 14500
},
{
"level": 31,
"required_point": 15000
},
{
"level": 32,
"required_point": 15500
},
{
"level": 33,
"required_point": 16000
},
{
"level": 34,
"required_point": 16500
},
{
"level": 35,
"required_point": 17000
},
{
"level": 36,
"required_point": 17500
},
{
"level": 37,
"required_point": 18000
},
{
"level": 38,
"required_point": 18500
},
{
"level": 39,
"required_point": 19000
},
{
"level": 40,
"required_point": 19500
},
{
"level": 41,
"required_point": 20000
},
{
"level": 42,
"required_point": 20500
},
{
"level": 43,
"required_point": 21000
},
{
"level": 44,
"required_point": 21500
},
{
"level": 45,
"required_point": 22000
},
{
"level": 46,
"required_point": 22500
},
{
"level": 47,
"required_point": 23000
},
{
"level": 48,
"required_point": 23500
},
{
"level": 49,
"required_point": 24000
},
{
"level": 50,
"required_point": 24500
},
{
"level": 51,
"required_point": 25000
},
{
"level": 52,
"required_point": 25500
},
{
"level": 53,
"required_point": 26000
},
{
"level": 54,
"required_point": 26500
},
{
"level": 55,
"required_point": 27000
},
{
"level": 56,
"required_point": 27500
},
{
"level": 57,
"required_point": 28000
},
{
"level": 58,
"required_point": 28500
},
{
"level": 59,
"required_point": 29000
},
{
"level": 60,
"required_point": 29500
},
{
"level": 61,
"required_point": 30000
},
{
"level": 62,
"required_point": 30500
},
{
"level": 63,
"required_point": 31000
},
{
"level": 64,
"required_point": 31500
},
{
"level": 65,
"required_point": 32000
},
{
"level": 66,
"required_point": 32500
},
{
"level": 67,
"required_point": 33000
},
{
"level": 68,
"required_point": 33500
},
{
"level": 69,
"required_point": 34000
},
{
"level": 70,
"required_point": 34500
},
{
"level": 71,
"required_point": 35000
},
{
"level": 72,
"required_point": 35500
},
{
"level": 73,
"required_point": 36000
},
{
"level": 74,
"required_point": 36500
},
{
"level": 75,
"required_point": 37000
},
{
"level": 76,
"required_point": 37500
},
{
"level": 77,
"required_point": 38000
},
{
"level": 78,
"required_point": 38500
},
{
"level": 79,
"required_point": 39000
},
{
"level": 80,
"required_point": 39500
},
{
"level": 81,
"required_point": 40000
},
{
"level": 82,
"required_point": 40500
},
{
"level": 83,
"required_point": 41000
},
{
"level": 84,
"required_point": 41500
},
{
"level": 85,
"required_point": 42000
},
{
"level": 86,
"required_point": 42500
},
{
"level": 87,
"required_point": 43000
},
{
"level": 88,
"required_point": 43500
},
{
"level": 89,
"required_point": 44000
},
{
"level": 90,
"required_point": 44500
},
{
"level": 91,
"required_point": 45000
},
{
"level": 92,
"required_point": 45500
},
{
"level": 93,
"required_point": 46000
},
{
"level": 94,
"required_point": 46500
},
{
"level": 95,
"required_point": 47000
},
{
"level": 96,
"required_point": 47500
},
{
"level": 97,
"required_point": 48000
},
{
"level": 98,
"required_point": 48500
},
{
"level": 99,
"required_point": 49000
},
{
"level": 100,
"required_point": 49500
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
[
{
"id": 23,
"name": "Season 23",
"max_level": 100,
"start_date": "2026-04-01T02:00:00+09:00",
"end_date": "2026-07-01T01:59:59+09:00",
"can_purchase": true,
"price_crystal": 980,
"description": "Unlock the Premium track and earn exclusive rewards through {0}."
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
{
"use_two_pick_premium_card": false,
"two_pick_sleeve_id": 3000011
}

View File

@@ -0,0 +1,20 @@
{
"id": 1,
"colosseum_id": "165",
"colosseum_name": "Rivenbrandt Take Two Cup",
"card_pool_name": "Take Two (DragonbladeRivenbrandt)",
"deck_format": "3",
"start_time": "2026-05-21 06:00:00",
"end_time": "2026-05-25 19:59:59",
"now_round": "1",
"is_display_tips": "0",
"tips_id": "0",
"is_colosseum_period": true,
"is_round_period": true,
"is_normal_two_pick": "1",
"is_special_mode": "10",
"is_all_card_enabled": 0,
"sales_period_info": {
"sales_period_time": "2026-05-25 19:59:59"
}
}

View File

@@ -0,0 +1,14 @@
[
{
"id": 1,
"bonus_data": []
},
{
"id": 3,
"bonus_data": []
},
{
"id": 4,
"bonus_data": []
}
]

View File

@@ -0,0 +1,394 @@
[
{
"id": 91,
"class_id": 1,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100111010,
100111010,
100111010,
100011020,
100011020,
100011020,
100012010,
100111020,
100111020,
100111020,
100111040,
100111040,
100111040,
100114010,
100114010,
100114010,
100011030,
100011030,
100011030,
100111060,
100111060,
100111060,
100011040,
100011040,
100011040,
100111030,
100111030,
100111030,
100111050,
100111050,
100111050,
100011050,
100011050,
100011050,
100111070,
100111070,
100111070,
100121010,
100121010,
100121010
]
},
{
"id": 92,
"class_id": 2,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100211010,
100211010,
100211010,
100011020,
100011020,
100011020,
100012010,
100211020,
100211020,
100211020,
100211060,
100211060,
100211060,
100214010,
100214010,
100214010,
100011030,
100011030,
100011030,
100211030,
100211030,
100211030,
100214020,
100214020,
100214020,
100011040,
100011040,
100011040,
100211040,
100211040,
100211040,
100011050,
100011050,
100011050,
100211050,
100211050,
100211050,
100221020,
100221020,
100221020
]
},
{
"id": 93,
"class_id": 3,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100314010,
100314010,
100314010,
100011020,
100011020,
100011020,
100012010,
100311010,
100311010,
100311010,
100314030,
100314030,
100314030,
100314020,
100314020,
100314020,
100314040,
100314040,
100314040,
100011030,
100011030,
100011030,
100314050,
100314050,
100314050,
100011040,
100011040,
100011040,
100314060,
100314060,
100314060,
100011050,
100011050,
100011050,
100314070,
100314070,
100314070,
100321010,
100321010,
100321010
]
},
{
"id": 94,
"class_id": 4,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100414020,
100414020,
100414020,
100011020,
100011020,
100011020,
100012010,
100411010,
100411010,
100411010,
100414010,
100414010,
100414010,
100011030,
100011030,
100011030,
100411050,
100411050,
100411050,
100011040,
100011040,
100011040,
100411030,
100411030,
100411030,
100414030,
100414030,
100414030,
100011050,
100011050,
100011050,
100411020,
100411020,
100411020,
100411040,
100411040,
100411040,
100421020,
100421020,
100421020
]
},
{
"id": 95,
"class_id": 5,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100011020,
100011020,
100011020,
100012010,
100511010,
100511010,
100511010,
100511020,
100511020,
100511020,
100514010,
100514010,
100514010,
100011030,
100011030,
100011030,
100511030,
100511030,
100511030,
100011040,
100011040,
100011040,
100511040,
100511040,
100511040,
100011050,
100011050,
100011050,
100511050,
100511050,
100511050,
100514020,
100514020,
100514020,
100511060,
100511060,
100511060,
100521030,
100521030,
100521030
]
},
{
"id": 96,
"class_id": 6,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100011020,
100011020,
100011020,
100012010,
100611010,
100611010,
100611010,
100611020,
100611020,
100611020,
100614010,
100614010,
100614010,
100614020,
100614020,
100614020,
100011030,
100011030,
100011030,
100611030,
100611030,
100611030,
100011040,
100011040,
100011040,
100611050,
100611050,
100611050,
100614030,
100614030,
100614030,
100011050,
100011050,
100011050,
100611040,
100611040,
100611040,
100621010,
100621010,
100621010
]
},
{
"id": 97,
"class_id": 7,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100713020,
100713020,
100713020,
100011020,
100011020,
100011020,
100012010,
100713010,
100713010,
100713010,
100711010,
100711010,
100711010,
100714010,
100714010,
100714010,
100714020,
100714020,
100714020,
100011030,
100011030,
100011030,
100713030,
100713030,
100713030,
100011040,
100011040,
100011040,
100011050,
100011050,
100011050,
100723010,
100723010,
100723010,
100714030,
100714030,
100714030,
100711020,
100711020,
100711020
]
},
{
"id": 98,
"class_id": 8,
"sleeve_id": 3000011,
"leader_skin_id": 0,
"deck_name": "Default",
"card_id_array": [
100011020,
100011020,
100011020,
100012010,
100811020,
100811020,
100811020,
100811060,
100811060,
100811060,
100811070,
100811070,
100811070,
100814010,
100814010,
100814010,
100011030,
100011030,
100011030,
100811010,
100811010,
100811010,
100811030,
100811030,
100811030,
100011040,
100011040,
100011040,
100811040,
100811040,
100811040,
100824010,
100824010,
100824010,
100011050,
100011050,
100011050,
100811050,
100811050,
100811050
]
}
]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,530 @@
[
{
"card_id": 100011020
},
{
"card_id": 101011030
},
{
"card_id": 101021020
},
{
"card_id": 101031040
},
{
"card_id": 101111010
},
{
"card_id": 101111020
},
{
"card_id": 101111050
},
{
"card_id": 100111050
},
{
"card_id": 101121080
},
{
"card_id": 101141010
},
{
"card_id": 101311060
},
{
"card_id": 101311070
},
{
"card_id": 101321030
},
{
"card_id": 101321050
},
{
"card_id": 101211010
},
{
"card_id": 100211060
},
{
"card_id": 101221020
},
{
"card_id": 101221100
},
{
"card_id": 101611010
},
{
"card_id": 101611040
},
{
"card_id": 100611010
},
{
"card_id": 101611060
},
{
"card_id": 101631060
},
{
"card_id": 101711030
},
{
"card_id": 100721010
},
{
"card_id": 101721040
},
{
"card_id": 101721070
},
{
"card_id": 101741030
},
{
"card_id": 100411010
},
{
"card_id": 100411030
},
{
"card_id": 101411080
},
{
"card_id": 101411100
},
{
"card_id": 101411110
},
{
"card_id": 101421100
},
{
"card_id": 101441010
},
{
"card_id": 100511030
},
{
"card_id": 101511050
},
{
"card_id": 101511060
},
{
"card_id": 100511050
},
{
"card_id": 101511090
},
{
"card_id": 101521040
},
{
"card_id": 101531010
},
{
"card_id": 101014030
},
{
"card_id": 101024030
},
{
"card_id": 101024040
},
{
"card_id": 101334030
},
{
"card_id": 101334040
},
{
"card_id": 101214010
},
{
"card_id": 101224010
},
{
"card_id": 100514020
},
{
"card_id": 101514030
},
{
"card_id": 101534010
},
{
"card_id": 100012010
},
{
"card_id": 101112010
},
{
"card_id": 101122020
},
{
"card_id": 101222010
},
{
"card_id": 101722010
},
{
"card_id": 101422010
},
{
"card_id": 900111010
},
{
"card_id": 900311010
},
{
"card_id": 900314010
},
{
"card_id": 900311030
},
{
"card_id": 900334010
},
{
"card_id": 900334020
},
{
"card_id": 900211020
},
{
"card_id": 900211050
},
{
"card_id": 900711030
},
{
"card_id": 900511010
},
{
"card_id": 900511020
},
{
"card_id": 900511030
},
{
"card_id": 900031010
},
{
"card_id": 103211050
},
{
"card_id": 107441020
},
{
"card_id": 100611020
},
{
"card_id": 100611040
},
{
"card_id": 100611050
},
{
"card_id": 100711010
},
{
"card_id": 100821020
},
{
"card_id": 101111060
},
{
"card_id": 101141030
},
{
"card_id": 101221090
},
{
"card_id": 101231030
},
{
"card_id": 101311090
},
{
"card_id": 101311100
},
{
"card_id": 101341020
},
{
"card_id": 101511100
},
{
"card_id": 101521050
},
{
"card_id": 101541010
},
{
"card_id": 101541030
},
{
"card_id": 101611130
},
{
"card_id": 101611140
},
{
"card_id": 101621020
},
{
"card_id": 101621030
},
{
"card_id": 101621060
},
{
"card_id": 101621070
},
{
"card_id": 101641010
},
{
"card_id": 101641030
},
{
"card_id": 101721100
},
{
"card_id": 102111040
},
{
"card_id": 102121010
},
{
"card_id": 102341010
},
{
"card_id": 102431010
},
{
"card_id": 102521010
},
{
"card_id": 102621010
},
{
"card_id": 103031010
},
{
"card_id": 103541010
},
{
"card_id": 103611060
},
{
"card_id": 104141010
},
{
"card_id": 104331020
},
{
"card_id": 104411020
},
{
"card_id": 104631020
},
{
"card_id": 104641010
},
{
"card_id": 105031010
},
{
"card_id": 105211020
},
{
"card_id": 105211030
},
{
"card_id": 106011010
},
{
"card_id": 106141010
},
{
"card_id": 107541010
},
{
"card_id": 107821030
},
{
"card_id": 107831010
},
{
"card_id": 701141010
},
{
"card_id": 701541010
},
{
"card_id": 701641010
},
{
"card_id": 703441010
},
{
"card_id": 810122010
},
{
"card_id": 810134010
},
{
"card_id": 810011010
},
{
"card_id": 810011020
},
{
"card_id": 810234010
},
{
"card_id": 810334010
},
{
"card_id": 810324010
},
{
"card_id": 810421010
},
{
"card_id": 810031010
},
{
"card_id": 810424010
},
{
"card_id": 810441010
},
{
"card_id": 810041010
},
{
"card_id": 810033010
},
{
"card_id": 810441020
},
{
"card_id": 810531010
},
{
"card_id": 810531020
},
{
"card_id": 810521010
},
{
"card_id": 810621010
},
{
"card_id": 810611010
},
{
"card_id": 810011030
},
{
"card_id": 810041020
},
{
"card_id": 810611020
},
{
"card_id": 810621020
},
{
"card_id": 810641010
},
{
"card_id": 810631010
},
{
"card_id": 810641020
},
{
"card_id": 810713010
},
{
"card_id": 810732010
},
{
"card_id": 810741010
},
{
"card_id": 810741020
},
{
"card_id": 810821010
},
{
"card_id": 810821020
},
{
"card_id": 820331010
},
{
"card_id": 820531010
},
{
"card_id": 820844010
},
{
"card_id": 820844020
},
{
"card_id": 810021010
},
{
"card_id": 820531020
},
{
"card_id": 820843010
},
{
"card_id": 820341010
},
{
"card_id": 820844030
},
{
"card_id": 820541010
},
{
"card_id": 820843020
},
{
"card_id": 820844040
},
{
"card_id": 810014010
},
{
"card_id": 810034010
},
{
"card_id": 820243010
},
{
"card_id": 820231010
},
{
"card_id": 820044010
},
{
"card_id": 820441010
},
{
"card_id": 820843030
},
{
"card_id": 127141030
}
]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,9 @@
[
{
"id": 119,
"period_num": 118,
"necessary_score": 0,
"begin_time": "2026-05-01 02:00:00",
"end_time": "2026-06-01 01:59:59"
}
]

View File

@@ -0,0 +1,74 @@
[
{
"id": 1,
"data": {
"ability_id": "1",
"add_start_pp": "0",
"add_start_life": "0",
"increase_add_pptotal_amount": "2",
"increase_add_pptotal_turn": "40",
"ability": "",
"ability_desc": "At the start of turns you can evolve, gain 2 play point orbs."
}
},
{
"id": 2,
"data": {
"ability_id": "2",
"add_start_pp": "0",
"add_start_life": "0",
"increase_add_pptotal_amount": "0",
"increase_add_pptotal_turn": "0",
"ability": "(skill:possess_ep_modifier)(timing:self_turn_start)(condition:{me.usable_ep}<=0&&evolvable_turn=true)(target:character=me&target=inplay&card_type=class)(option:add_ep=2)(preprocess:remove_after_action=(count=1))(effect_path:btl_ep_cure_1)(se_path:se_btl_ep_cure_1)(effect_move_type:DIRECT_EPPANEL_SELF)(engine_type:SHURIKEN)(effect_time:0.5)(effect_target_type:single)",
"ability_desc": "At the start of turns you can evolve, if you have 0 evolution points, recover 2. [1 time]"
}
},
{
"id": 3,
"data": {
"ability_id": "3",
"add_start_pp": "0",
"add_start_life": "0",
"increase_add_pptotal_amount": "0",
"increase_add_pptotal_turn": "0",
"ability": "(skill:draw)(timing:self_turn_start)(condition:{me.inplay.class.turn}=1)(target:character=me&target=deck&card_type=all&random_count=2)(option:none)(preprocess:remove_after_action=(count=1))",
"ability_desc": "At the start of your first turn, draw 2 cards."
}
},
{
"id": 4,
"data": {
"ability_id": "4",
"add_start_pp": "0",
"add_start_life": "0",
"increase_add_pptotal_amount": "1",
"increase_add_pptotal_turn": "40",
"ability": "",
"ability_desc": "At the start of turns you can evolve, gain a play point orb."
}
},
{
"id": 5,
"data": {
"ability_id": "5",
"add_start_pp": "0",
"add_start_life": "0",
"increase_add_pptotal_amount": "0",
"increase_add_pptotal_turn": "0",
"ability": "(skill:possess_ep_modifier)(timing:self_turn_start)(condition:{me.usable_ep}<=0&&evolvable_turn=true)(target:character=me&target=inplay&card_type=class)(option:add_ep=1)(preprocess:remove_after_action=(count=1))(effect_path:btl_ep_cure_1)(se_path:se_btl_ep_cure_1)(effect_move_type:DIRECT_EPPANEL_SELF)(engine_type:SHURIKEN)(effect_time:0.5)(effect_target_type:single)",
"ability_desc": "At the start of turns you can evolve, if you have 0 evolution points, recover 1. [1 time]"
}
},
{
"id": 6,
"data": {
"ability_id": "6",
"add_start_pp": "0",
"add_start_life": "0",
"increase_add_pptotal_amount": "0",
"increase_add_pptotal_turn": "0",
"ability": "(skill:draw)(timing:self_turn_start)(condition:{me.inplay.class.turn}=1)(target:character=me&target=deck&card_type=all&random_count=1)(option:none)(preprocess:remove_after_action=(count=1))",
"ability_desc": "At the start of your first turn, draw a card."
}
}
]

View File

@@ -0,0 +1,10 @@
{
"gathering": {
"begin": "2024-05-01 20:00:00",
"end": "2030-06-26 19:59:59"
},
"free_battle": {
"begin": "2024-05-01 20:00:00",
"end": "2030-06-26 19:59:59"
}
}

View File

@@ -0,0 +1,191 @@
[
{
"id": 10006,
"card_set_ids_csv": "10000|10001|10002|10003|10004|10005|10006",
"abilities_csv": "1|2|3",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101114050\": 101114050, \"101141020\": 101141020, \"101241010\": 101241010, \"101314020\": 101314020, \"101341020\": 101341020, \"101431030\": 101431030, \"101441020\": 101441020, \"101514010\": 101514010, \"101541010\": 101541010, \"101614020\": 101614020, \"101641010\": 101641010, \"101741030\": 101741030, \"102312040\": 102312040, \"103241010\": 103241010, \"103541010\": 103541010, \"104141020\": 104141020, \"104341020\": 104341020, \"104441010\": 104441010, \"104641010\": 104641010, \"104741020\": 104741020, \"105312010\": 105312010, \"106114010\": 106114010, \"106221010\": 106221010, \"106324010\": 106324010, \"106434010\": 106434010, \"106511010\": 106511010, \"106624010\": 106624010, \"106721030\": 106721030}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10007,
"card_set_ids_csv": "10000|10003|10004|10005|10006|10007",
"abilities_csv": "1|2|3",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"103241010\": 103241010, \"103541010\": 103541010, \"104141020\": 104141020, \"104341020\": 104341020, \"104441010\": 104441010, \"104641010\": 104641010, \"104741020\": 104741020, \"105312010\": 105312010, \"106114010\": 106114010, \"106221010\": 106221010, \"106324010\": 106324010, \"106434010\": 106434010, \"106511010\": 106511010, \"106624010\": 106624010, \"106721030\": 106721030, \"107322010\": 107322010, \"107732010\": 107732010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10008,
"card_set_ids_csv": "10000|10004|10005|10006|10007|10008",
"abilities_csv": "1|2|3",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101614020\": 101614020, \"104141020\": 104141020, \"104341020\": 104341020, \"104441010\": 104441010, \"104641010\": 104641010, \"104741020\": 104741020, \"105312010\": 105312010, \"106114010\": 106114010, \"106221010\": 106221010, \"106324010\": 106324010, \"106434010\": 106434010, \"106511010\": 106511010, \"106624010\": 106624010, \"106721030\": 106721030, \"107322010\": 107322010, \"107732010\": 107732010, \"108031020\": 108031020}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10009,
"card_set_ids_csv": "10000|10005|10006|10007|10008|10009",
"abilities_csv": "1|2|3",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101314020\": 101314020, \"101514010\": 101514010, \"101614020\": 101614020, \"105312010\": 105312010, \"106114010\": 106114010, \"106221010\": 106221010, \"106324010\": 106324010, \"106434010\": 106434010, \"106511010\": 106511010, \"106624010\": 106624010, \"106721030\": 106721030, \"107322010\": 107322010, \"107732010\": 107732010, \"108031020\": 108031020, \"109034010\": 109034010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10010,
"card_set_ids_csv": "10000|10006|10007|10008|10009|10010",
"abilities_csv": "1|2|3",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114050\": 101114050, \"101314020\": 101314020, \"101514010\": 101514010, \"101614020\": 101614020, \"102312040\": 102312040, \"106114010\": 106114010, \"106221010\": 106221010, \"106324010\": 106324010, \"106434010\": 106434010, \"106511010\": 106511010, \"106624010\": 106624010, \"106721030\": 106721030, \"107322010\": 107322010, \"107732010\": 107732010, \"108031020\": 108031020, \"109034010\": 109034010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10011,
"card_set_ids_csv": "10000|10007|10008|10009|10010|10011",
"abilities_csv": "1|2|3",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114050\": 101114050, \"101314020\": 101314020, \"101514010\": 101514010, \"101614020\": 101614020, \"102312040\": 102312040, \"107322010\": 107322010, \"107732010\": 107732010, \"108031020\": 108031020, \"109034010\": 109034010, \"111124010\": 111124010, \"111214010\": 111214010, \"111314010\": 111314010, \"111434010\": 111434010, \"111514010\": 111514010, \"111634010\": 111634010, \"111734010\": 111734010, \"111814010\": 111814010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10012,
"card_set_ids_csv": "10000|10008|10009|10010|10011|10012",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101114050\": 101114050, \"101314020\": 101314020, \"101514010\": 101514010, \"101614020\": 101614020, \"102312040\": 102312040, \"108031020\": 108031020, \"109034010\": 109034010, \"111124010\": 111124010, \"111214010\": 111214010, \"111314010\": 111314010, \"111434010\": 111434010, \"111514010\": 111514010, \"111634010\": 111634010, \"111734010\": 111734010, \"111814010\": 111814010, \"112031010\": 112031010, \"112122010\": 112122010, \"112222010\": 112222010, \"112224010\": 112224010, \"112322010\": 112322010, \"112414010\": 112414010, \"112422010\": 112422010, \"112522010\": 112522010, \"112622010\": 112622010, \"112722010\": 112722010, \"112822010\": 112822010, \"112834010\": 112834010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10013,
"card_set_ids_csv": "10000|10009|10010|10011|10012|10013",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101114050\": 101114050, \"101314020\": 101314020, \"101514010\": 101514010, \"102312040\": 102312040, \"105312010\": 105312010, \"109034010\": 109034010, \"111124010\": 111124010, \"111214010\": 111214010, \"111314010\": 111314010, \"111434010\": 111434010, \"111514010\": 111514010, \"111634010\": 111634010, \"111734010\": 111734010, \"111814010\": 111814010, \"112031010\": 112031010, \"112122010\": 112122010, \"112222010\": 112222010, \"112224010\": 112224010, \"112322010\": 112322010, \"112414010\": 112414010, \"112422010\": 112422010, \"112522010\": 112522010, \"112622010\": 112622010, \"112722010\": 112722010, \"112822010\": 112822010, \"112834010\": 112834010, \"113034010\": 113034010, \"113114010\": 113114010, \"113224010\": 113224010, \"113614010\": 113614010, \"113733010\": 113733010, \"113824010\": 113824010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10014,
"card_set_ids_csv": "10000|10010|10011|10012|10013|10014",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101114050\": 101114050, \"102312040\": 102312040, \"105312010\": 105312010, \"106114010\": 106114010, \"107322010\": 107322010, \"111124010\": 111124010, \"111214010\": 111214010, \"111314010\": 111314010, \"111434010\": 111434010, \"111514010\": 111514010, \"111634010\": 111634010, \"111734010\": 111734010, \"111814010\": 111814010, \"112031010\": 112031010, \"112122010\": 112122010, \"112222010\": 112222010, \"112224010\": 112224010, \"112322010\": 112322010, \"112414010\": 112414010, \"112422010\": 112422010, \"112522010\": 112522010, \"112622010\": 112622010, \"112722010\": 112722010, \"112822010\": 112822010, \"112834010\": 112834010, \"113034010\": 113034010, \"113114010\": 113114010, \"113224010\": 113224010, \"113614010\": 113614010, \"113733010\": 113733010, \"113824010\": 113824010, \"114014010\": 114014010, \"114031010\": 114031010, \"114234010\": 114234010, \"114834010\": 114834010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10015,
"card_set_ids_csv": "10000|10011|10012|10013|10014|10015",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"105312010\": 105312010, \"106114010\": 106114010, \"107322010\": 107322010, \"111124010\": 111124010, \"111214010\": 111214010, \"111314010\": 111314010, \"111434010\": 111434010, \"111514010\": 111514010, \"111634010\": 111634010, \"111734010\": 111734010, \"111814010\": 111814010, \"112031010\": 112031010, \"112122010\": 112122010, \"112222010\": 112222010, \"112224010\": 112224010, \"112322010\": 112322010, \"112414010\": 112414010, \"112422010\": 112422010, \"112522010\": 112522010, \"112622010\": 112622010, \"112722010\": 112722010, \"112822010\": 112822010, \"112834010\": 112834010, \"113034010\": 113034010, \"113114010\": 113114010, \"113224010\": 113224010, \"113614010\": 113614010, \"113733010\": 113733010, \"113824010\": 113824010, \"114014010\": 114014010, \"114031010\": 114031010, \"114234010\": 114234010, \"114834010\": 114834010, \"115124010\": 115124010, \"115214010\": 115214010, \"115414010\": 115414010, \"115814010\": 115814010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10016,
"card_set_ids_csv": "10000|10012|10013|10014|10015|10016",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"105312010\": 105312010, \"106114010\": 106114010, \"107322010\": 107322010, \"112031010\": 112031010, \"112122010\": 112122010, \"112222010\": 112222010, \"112224010\": 112224010, \"112322010\": 112322010, \"112414010\": 112414010, \"112422010\": 112422010, \"112522010\": 112522010, \"112622010\": 112622010, \"112722010\": 112722010, \"112822010\": 112822010, \"112834010\": 112834010, \"113034010\": 113034010, \"113114010\": 113114010, \"113224010\": 113224010, \"113614010\": 113614010, \"113733010\": 113733010, \"113824010\": 113824010, \"114014010\": 114014010, \"114031010\": 114031010, \"114234010\": 114234010, \"114834010\": 114834010, \"115124010\": 115124010, \"115214010\": 115214010, \"115414010\": 115414010, \"115814010\": 115814010, \"116524010\": 116524010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10017,
"card_set_ids_csv": "10000|10013|10014|10015|10016|10017",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"105312010\": 105312010, \"106114010\": 106114010, \"107322010\": 107322010, \"113034010\": 113034010, \"113114010\": 113114010, \"113224010\": 113224010, \"113614010\": 113614010, \"113733010\": 113733010, \"113824010\": 113824010, \"114014010\": 114014010, \"114031010\": 114031010, \"114234010\": 114234010, \"114834010\": 114834010, \"115124010\": 115124010, \"115214010\": 115214010, \"115414010\": 115414010, \"115814010\": 115814010, \"116524010\": 116524010, \"117034010\": 117034010, \"117124010\": 117124010, \"117234010\": 117234010, \"117434010\": 117434010, \"117624010\": 117624010, \"117724010\": 117724010, \"117824010\": 117824010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10018,
"card_set_ids_csv": "10000|10014|10015|10016|10017|10018",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"106114010\": 106114010, \"107322010\": 107322010, \"114014010\": 114014010, \"114031010\": 114031010, \"114234010\": 114234010, \"114834010\": 114834010, \"115124010\": 115124010, \"115214010\": 115214010, \"115414010\": 115414010, \"115814010\": 115814010, \"116524010\": 116524010, \"117034010\": 117034010, \"117124010\": 117124010, \"117234010\": 117234010, \"117434010\": 117434010, \"117624010\": 117624010, \"117724010\": 117724010, \"117824010\": 117824010, \"118011020\": 118011020, \"118424010\": 118424010, \"118524010\": 118524010, \"118531010\": 118531010, \"118624010\": 118624010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10019,
"card_set_ids_csv": "10000|10015|10016|10017|10018|10019",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"107732010\": 107732010, \"115124010\": 115124010, \"115214010\": 115214010, \"115414010\": 115414010, \"115814010\": 115814010, \"116524010\": 116524010, \"117034010\": 117034010, \"117124010\": 117124010, \"117234010\": 117234010, \"117434010\": 117434010, \"117624010\": 117624010, \"117724010\": 117724010, \"117824010\": 117824010, \"118011020\": 118011020, \"118424010\": 118424010, \"118524010\": 118524010, \"118531010\": 118531010, \"118624010\": 118624010, \"119031030\": 119031030, \"119314010\": 119314010, \"119424010\": 119424010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10020,
"card_set_ids_csv": "10000|10016|10017|10018|10019|10020",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"107732010\": 107732010, \"116524010\": 116524010, \"117034010\": 117034010, \"117124010\": 117124010, \"117234010\": 117234010, \"117434010\": 117434010, \"117624010\": 117624010, \"117724010\": 117724010, \"117824010\": 117824010, \"118011020\": 118011020, \"118424010\": 118424010, \"118524010\": 118524010, \"118531010\": 118531010, \"118624010\": 118624010, \"119031030\": 119031030, \"119314010\": 119314010, \"119424010\": 119424010, \"120014010\": 120014010, \"120134010\": 120134010, \"120214010\": 120214010, \"120221020\": 120221020, \"120611010\": 120611010, \"120721020\": 120721020, \"120821020\": 120821020}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10021,
"card_set_ids_csv": "10000|10017|10018|10019|10020|10021",
"abilities_csv": "4|5|6",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"107732010\": 107732010, \"117034010\": 117034010, \"117124010\": 117124010, \"117234010\": 117234010, \"117434010\": 117434010, \"117624010\": 117624010, \"117724010\": 117724010, \"117824010\": 117824010, \"118011020\": 118011020, \"118424010\": 118424010, \"118524010\": 118524010, \"118531010\": 118531010, \"118624010\": 118624010, \"119031030\": 119031030, \"119314010\": 119314010, \"119424010\": 119424010, \"120014010\": 120014010, \"120134010\": 120134010, \"120214010\": 120214010, \"120221020\": 120221020, \"120611010\": 120611010, \"120721020\": 120721020, \"120821020\": 120821020, \"121031010\": 121031010, \"121221010\": 121221010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10022,
"card_set_ids_csv": "10000|10018|10019|10020|10021|10022",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"107732010\": 107732010, \"118011020\": 118011020, \"118424010\": 118424010, \"118524010\": 118524010, \"118531010\": 118531010, \"118624010\": 118624010, \"119031030\": 119031030, \"119314010\": 119314010, \"119424010\": 119424010, \"120014010\": 120014010, \"120134010\": 120134010, \"120214010\": 120214010, \"120221020\": 120221020, \"120611010\": 120611010, \"120721020\": 120721020, \"120821020\": 120821020, \"121031010\": 121031010, \"121221010\": 121221010, \"122121030\": 122121030, \"122334010\": 122334010, \"122514010\": 122514010, \"122714010\": 122714010, \"122811020\": 122811020}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10023,
"card_set_ids_csv": "10000|10019|10020|10021|10022|10023",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"107732010\": 107732010, \"119031030\": 119031030, \"119314010\": 119314010, \"119424010\": 119424010, \"120014010\": 120014010, \"120134010\": 120134010, \"120214010\": 120214010, \"120221020\": 120221020, \"120611010\": 120611010, \"120721020\": 120721020, \"120821020\": 120821020, \"121031010\": 121031010, \"121221010\": 121221010, \"122121030\": 122121030, \"122334010\": 122334010, \"122514010\": 122514010, \"122714010\": 122714010, \"122811020\": 122811020, \"123114010\": 123114010, \"123231010\": 123231010, \"123314010\": 123314010, \"123331020\": 123331020, \"123414010\": 123414010, \"123514010\": 123514010, \"123614010\": 123614010, \"123814010\": 123814010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10024,
"card_set_ids_csv": "10000|10020|10021|10022|10023|10024",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"120014010\": 120014010, \"120134010\": 120134010, \"120214010\": 120214010, \"120221020\": 120221020, \"120611010\": 120611010, \"120721020\": 120721020, \"120821020\": 120821020, \"121031010\": 121031010, \"121221010\": 121221010, \"122121030\": 122121030, \"122334010\": 122334010, \"122514010\": 122514010, \"122714010\": 122714010, \"122811020\": 122811020, \"123114010\": 123114010, \"123231010\": 123231010, \"123314010\": 123314010, \"123331020\": 123331020, \"123414010\": 123414010, \"123514010\": 123514010, \"123614010\": 123614010, \"123814010\": 123814010, \"124024010\": 124024010, \"124121020\": 124121020, \"124131010\": 124131010, \"124134010\": 124134010, \"124214010\": 124214010, \"124221020\": 124221020, \"124324010\": 124324010, \"124421020\": 124421020, \"124524010\": 124524010, \"124624010\": 124624010, \"124721020\": 124721020, \"124731010\": 124731010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10025,
"card_set_ids_csv": "10000|10021|10022|10023|10024|10025",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"121031010\": 121031010, \"121221010\": 121221010, \"122121030\": 122121030, \"122334010\": 122334010, \"122514010\": 122514010, \"122714010\": 122714010, \"122811020\": 122811020, \"123114010\": 123114010, \"123231010\": 123231010, \"123314010\": 123314010, \"123331020\": 123331020, \"123414010\": 123414010, \"123514010\": 123514010, \"123614010\": 123614010, \"123814010\": 123814010, \"124024010\": 124024010, \"124121020\": 124121020, \"124131010\": 124131010, \"124134010\": 124134010, \"124214010\": 124214010, \"124221020\": 124221020, \"124324010\": 124324010, \"124421020\": 124421020, \"124524010\": 124524010, \"124624010\": 124624010, \"124721020\": 124721020, \"124731010\": 124731010, \"125011010\": 125011010, \"125414010\": 125414010, \"125614010\": 125614010, \"125733010\": 125733010, \"125811030\": 125811030, \"125834010\": 125834010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10026,
"card_set_ids_csv": "10000|10022|10023|10024|10025|10026",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"122121030\": 122121030, \"122334010\": 122334010, \"122514010\": 122514010, \"122714010\": 122714010, \"122811020\": 122811020, \"123114010\": 123114010, \"123231010\": 123231010, \"123314010\": 123314010, \"123331020\": 123331020, \"123414010\": 123414010, \"123514010\": 123514010, \"123614010\": 123614010, \"123814010\": 123814010, \"124024010\": 124024010, \"124121020\": 124121020, \"124131010\": 124131010, \"124134010\": 124134010, \"124214010\": 124214010, \"124221020\": 124221020, \"124324010\": 124324010, \"124421020\": 124421020, \"124524010\": 124524010, \"124624010\": 124624010, \"124721020\": 124721020, \"124731010\": 124731010, \"125011010\": 125011010, \"125414010\": 125414010, \"125614010\": 125614010, \"125733010\": 125733010, \"125811030\": 125811030, \"125834010\": 125834010, \"126031020\": 126031020, \"126114010\": 126114010, \"126234010\": 126234010, \"126334010\": 126334010, \"126411030\": 126411030, \"126424010\": 126424010, \"126514010\": 126514010, \"126521020\": 126521020, \"126614010\": 126614010, \"126732010\": 126732010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10027,
"card_set_ids_csv": "10000|10023|10024|10025|10026|10027",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"123114010\": 123114010, \"123231010\": 123231010, \"123314010\": 123314010, \"123331020\": 123331020, \"123414010\": 123414010, \"123514010\": 123514010, \"123614010\": 123614010, \"123814010\": 123814010, \"124024010\": 124024010, \"124121020\": 124121020, \"124131010\": 124131010, \"124134010\": 124134010, \"124214010\": 124214010, \"124221020\": 124221020, \"124324010\": 124324010, \"124421020\": 124421020, \"124524010\": 124524010, \"124624010\": 124624010, \"124721020\": 124721020, \"124731010\": 124731010, \"125011010\": 125011010, \"125414010\": 125414010, \"125614010\": 125614010, \"125733010\": 125733010, \"125811030\": 125811030, \"125834010\": 125834010, \"126031020\": 126031020, \"126114010\": 126114010, \"126234010\": 126234010, \"126334010\": 126334010, \"126411030\": 126411030, \"126424010\": 126424010, \"126514010\": 126514010, \"126521020\": 126521020, \"126614010\": 126614010, \"126732010\": 126732010, \"127134010\": 127134010, \"127141030\": 127141030, \"127314010\": 127314010, \"127614010\": 127614010, \"127713010\": 127713010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10028,
"card_set_ids_csv": "10000|10024|10025|10026|10027|10028",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"124024010\": 124024010, \"124121020\": 124121020, \"124131010\": 124131010, \"124134010\": 124134010, \"124214010\": 124214010, \"124221020\": 124221020, \"124324010\": 124324010, \"124421020\": 124421020, \"124524010\": 124524010, \"124624010\": 124624010, \"124721020\": 124721020, \"124731010\": 124731010, \"125011010\": 125011010, \"125414010\": 125414010, \"125614010\": 125614010, \"125733010\": 125733010, \"125811030\": 125811030, \"125834010\": 125834010, \"126031020\": 126031020, \"126114010\": 126114010, \"126234010\": 126234010, \"126334010\": 126334010, \"126411030\": 126411030, \"126424010\": 126424010, \"126514010\": 126514010, \"126521020\": 126521020, \"126614010\": 126614010, \"126732010\": 126732010, \"127134010\": 127134010, \"127141030\": 127141030, \"127314010\": 127314010, \"127614010\": 127614010, \"127713010\": 127713010, \"128224010\": 128224010, \"128424010\": 128424010, \"128514010\": 128514010, \"128614010\": 128614010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10029,
"card_set_ids_csv": "10000|10025|10026|10027|10028|10029",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"125011010\": 125011010, \"125414010\": 125414010, \"125614010\": 125614010, \"125733010\": 125733010, \"125811030\": 125811030, \"125834010\": 125834010, \"126031020\": 126031020, \"126114010\": 126114010, \"126234010\": 126234010, \"126334010\": 126334010, \"126411030\": 126411030, \"126424010\": 126424010, \"126514010\": 126514010, \"126521020\": 126521020, \"126614010\": 126614010, \"126732010\": 126732010, \"127134010\": 127134010, \"127141030\": 127141030, \"127314010\": 127314010, \"127614010\": 127614010, \"127713010\": 127713010, \"128224010\": 128224010, \"128424010\": 128424010, \"128514010\": 128514010, \"128614010\": 128614010, \"129024010\": 129024010, \"129211010\": 129211010, \"129214010\": 129214010, \"129314020\": 129314020, \"129321010\": 129321010, \"129421010\": 129421010, \"129713020\": 129713020}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10030,
"card_set_ids_csv": "10000|10026|10027|10028|10029|10030",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101314020\": 101314020, \"126031020\": 126031020, \"126114010\": 126114010, \"126234010\": 126234010, \"126334010\": 126334010, \"126411030\": 126411030, \"126424010\": 126424010, \"126514010\": 126514010, \"126521020\": 126521020, \"126614010\": 126614010, \"126732010\": 126732010, \"127134010\": 127134010, \"127141030\": 127141030, \"127314010\": 127314010, \"127614010\": 127614010, \"127713010\": 127713010, \"128224010\": 128224010, \"128424010\": 128424010, \"128514010\": 128514010, \"128614010\": 128614010, \"129024010\": 129024010, \"129211010\": 129211010, \"129214010\": 129214010, \"129314020\": 129314020, \"129321010\": 129321010, \"129421010\": 129421010, \"129713020\": 129713020, \"130024010\": 130024010, \"130141020\": 130141020, \"130241020\": 130241020, \"130341020\": 130341020, \"130641020\": 130641020}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10031,
"card_set_ids_csv": "10000|10027|10028|10029|10030|10031",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101314020\": 101314020, \"127134010\": 127134010, \"127141030\": 127141030, \"127314010\": 127314010, \"127614010\": 127614010, \"127713010\": 127713010, \"128224010\": 128224010, \"128424010\": 128424010, \"128514010\": 128514010, \"128614010\": 128614010, \"129024010\": 129024010, \"129211010\": 129211010, \"129214010\": 129214010, \"129314020\": 129314020, \"129321010\": 129321010, \"129421010\": 129421010, \"129713020\": 129713020, \"130024010\": 130024010, \"130141020\": 130141020, \"130241020\": 130241020, \"130341020\": 130341020, \"130641020\": 130641020, \"131031020\": 131031020, \"131723010\": 131723010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
},
{
"id": 10032,
"card_set_ids_csv": "10000|10028|10029|10030|10031|10032",
"abilities_csv": "",
"reprinted_card_ids": "{\"100114010\": 100114010, \"100211010\": 100211010, \"100214010\": 100214010, \"100214020\": 100214020, \"100314010\": 100314010, \"100314020\": 100314020, \"100314030\": 100314030, \"100314040\": 100314040, \"100314070\": 100314070, \"100414010\": 100414010, \"100514010\": 100514010, \"100614010\": 100614010, \"100614020\": 100614020, \"100714010\": 100714010, \"100714020\": 100714020, \"100814010\": 100814010, \"101114010\": 101114010, \"101314020\": 101314020, \"128224010\": 128224010, \"128424010\": 128424010, \"128514010\": 128514010, \"128614010\": 128614010, \"129024010\": 129024010, \"129211010\": 129211010, \"129214010\": 129214010, \"129314020\": 129314020, \"129321010\": 129321010, \"129421010\": 129421010, \"129713020\": 129713020, \"130024010\": 130024010, \"130141020\": 130141020, \"130241020\": 130241020, \"130341020\": 130341020, \"130641020\": 130641020, \"131031020\": 131031020, \"131723010\": 131723010}",
"restricted_card_ids": "{\"123041020\": \"0\"}"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
[
{
"record_id": 3,
"product_id": 992,
"store_product_id": 99200,
"name": "[b]One-time Deal![/b] 800-crystal set",
"text": "Purchase 800 Crystals",
"price": "7.99",
"charge_crystal_num": 800,
"free_crystal_num": 0,
"purchase_limit": 1,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal_strong",
"start_time": "2018-01-30 04:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 10,
"product_id": 994,
"store_product_id": 99400,
"name": "[b]Special Offer![/b] 7500-crystal set (3 times per person)",
"text": "Purchase 7500 Crystals",
"price": "79.99",
"charge_crystal_num": 7500,
"free_crystal_num": 0,
"purchase_limit": 3,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal_strong",
"start_time": "2017-06-01 06:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 19,
"product_id": 989,
"store_product_id": 98900,
"name": "[b]1-Time Deal![/b] 1000-crystal set",
"text": "Purchase 1000 Crystals",
"price": "15.99",
"charge_crystal_num": 1000,
"free_crystal_num": 0,
"purchase_limit": 1,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal_strong",
"start_time": "2026-04-01 02:00:00",
"end_time": "2026-07-01 01:59:59",
"remaining_time": 0,
"is_resale_product": 1,
"resale_start_date": "2026-04-01 02:00:00"
},
{
"record_id": 21,
"product_id": 8,
"store_product_id": 10011,
"name": "60-crystal set",
"text": "Purchase 60 Crystals",
"price": "0.99",
"charge_crystal_num": 60,
"free_crystal_num": 0,
"purchase_limit": 999999999,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal",
"start_time": "2022-10-05 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 24,
"product_id": 9,
"store_product_id": 70011,
"name": "350-crystal set",
"text": "Purchase 350 Crystals",
"price": "5.99",
"charge_crystal_num": 350,
"free_crystal_num": 0,
"purchase_limit": 999999999,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal",
"start_time": "2022-10-05 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 26,
"product_id": 10,
"store_product_id": 30011,
"name": "670-crystal set",
"text": "Purchase 670 Crystals",
"price": "10.99",
"charge_crystal_num": 670,
"free_crystal_num": 0,
"purchase_limit": 999999999,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal",
"start_time": "2022-10-05 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 27,
"product_id": 4,
"store_product_id": 40000,
"name": "1200-crystal set",
"text": "Purchase 1200 Crystals",
"price": "20.99",
"charge_crystal_num": 1200,
"free_crystal_num": 0,
"purchase_limit": 999999999,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal",
"start_time": "2015-03-01 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 28,
"product_id": 5,
"store_product_id": 50000,
"name": "2400-crystal set",
"text": "Purchase 2400 Crystals",
"price": "39.99",
"charge_crystal_num": 2400,
"free_crystal_num": 0,
"purchase_limit": 999999999,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal",
"start_time": "2015-03-01 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 29,
"product_id": 6,
"store_product_id": 60000,
"name": "5000-crystal set",
"text": "Purchase 5000 Crystals",
"price": "79.99",
"charge_crystal_num": 5000,
"free_crystal_num": 0,
"purchase_limit": 999999999,
"special_shop_flag": 0,
"image_name": "thumbnail_crystal",
"start_time": "2015-03-01 15:00:00",
"end_time": "2030-03-01 14:59:59",
"remaining_time": 0,
"is_resale_product": 0,
"resale_start_date": ""
},
{
"record_id": 30,
"product_id": 800,
"store_product_id": 80000,
"name": "1200-crystal and Legendary set",
"text": "Purchase 1200 Crystals and Legendary set",
"price": "20.99",
"charge_crystal_num": 1200,
"free_crystal_num": 0,
"purchase_limit": 3,
"special_shop_flag": 1,
"image_name": "thumbnail_crystal_strong",
"start_time": "2018-01-01 00:00:00",
"end_time": "2019-03-19 16:15:17",
"remaining_time": 604800,
"is_resale_product": 0,
"resale_start_date": ""
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
{
"pre_release_id": "1",
"next_card_set_id": "10018",
"start_time": "1900-12-28 00:00:00",
"end_time": "2019-09-29 05:29:59",
"display_end_time": "2020-10-03 14:59:59",
"free_match_start_time": "1900-12-28 02:00:00",
"card_master_id": 1,
"default_card_master_id": "2",
"pre_release_card_master_id": "1",
"is_pre_rotation_free_match_term": false,
"rotation_card_set_id_list": [
0
],
"reprinted_base_card_ids": {
"100114010": "100114010",
"100211010": "100211010",
"100214010": "100214010",
"100214020": "100214020",
"100314010": "100314010",
"100314020": "100314020",
"100314030": "100314030",
"100314040": "100314040",
"100314070": "100314070",
"100414010": "100414010",
"100514010": "100514010",
"100614010": "100614010",
"100614020": "100614020",
"100714010": "100714010",
"100714020": "100714020",
"100814010": "100814010",
"101114010": "101114010",
"105312010": "105312010",
"106114010": "106114010",
"107322010": "107322010",
"111124010": "111124010",
"111214010": "111214010",
"111314010": "111314010",
"111434010": "111434010",
"111514010": "111514010",
"111634010": "111634010",
"111734010": "111734010",
"111814010": "111814010",
"112031010": "112031010",
"112122010": "112122010",
"112222010": "112222010",
"112224010": "112224010",
"112322010": "112322010",
"112414010": "112414010",
"112422010": "112422010",
"112522010": "112522010",
"112622010": "112622010",
"112722010": "112722010",
"112822010": "112822010",
"112834010": "112834010",
"113034010": "113034010",
"113114010": "113114010",
"113224010": "113224010",
"113614010": "113614010",
"113733010": "113733010",
"113824010": "113824010",
"114014010": "114014010",
"114031010": "114031010",
"114234010": "114234010",
"114834010": "114834010",
"115124010": "115124010",
"115214010": "115214010",
"115414010": "115414010",
"115814010": "115814010"
},
"latest_reprinted_base_card_ids": []
}

View File

@@ -0,0 +1,305 @@
[
{
"id": 316,
"basic_title_text_id": "Puzzle_QuestSelect_0316",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Expert": "2"
}
},
{
"id": 315,
"basic_title_text_id": "Puzzle_QuestSelect_0315",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 314,
"basic_title_text_id": "Puzzle_QuestSelect_0314",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 313,
"basic_title_text_id": "Puzzle_QuestSelect_0313",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 312,
"basic_title_text_id": "Puzzle_QuestSelect_0312",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 311,
"basic_title_text_id": "Puzzle_QuestSelect_0311",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 310,
"basic_title_text_id": "Puzzle_QuestSelect_0310",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 309,
"basic_title_text_id": "Puzzle_QuestSelect_0309",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 308,
"basic_title_text_id": "Puzzle_QuestSelect_0308",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 307,
"basic_title_text_id": "Puzzle_QuestSelect_0307",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 306,
"basic_title_text_id": "Puzzle_QuestSelect_0306",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 305,
"basic_title_text_id": "Puzzle_QuestSelect_0305",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 304,
"basic_title_text_id": "Puzzle_QuestSelect_0304",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 303,
"basic_title_text_id": "Puzzle_QuestSelect_0303",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 302,
"basic_title_text_id": "Puzzle_QuestSelect_0302",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 301,
"basic_title_text_id": "Puzzle_QuestSelect_0301",
"puzzle_chara_id": 3704,
"chara_id": 3704,
"sort_type": 1,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 9,
"basic_title_text_id": "Puzzle_QuestSelect_0109",
"puzzle_chara_id": 600090,
"chara_id": 600090,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2",
"": "3"
}
},
{
"id": 8,
"basic_title_text_id": "Puzzle_QuestSelect_0108",
"puzzle_chara_id": 600080,
"chara_id": 600080,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2",
"": "3"
}
},
{
"id": 7,
"basic_title_text_id": "Puzzle_QuestSelect_0107",
"puzzle_chara_id": 600070,
"chara_id": 600070,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 6,
"basic_title_text_id": "Puzzle_QuestSelect_0106",
"puzzle_chara_id": 600060,
"chara_id": 600060,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2",
"": "3"
}
},
{
"id": 5,
"basic_title_text_id": "Puzzle_QuestSelect_0105",
"puzzle_chara_id": 3801,
"chara_id": 3801,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 4,
"basic_title_text_id": "Puzzle_QuestSelect_0104",
"puzzle_chara_id": 3603,
"chara_id": 3603,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2",
"": "3"
}
},
{
"id": 3,
"basic_title_text_id": "Puzzle_QuestSelect_0103",
"puzzle_chara_id": 3403,
"chara_id": 3403,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
},
{
"id": 2,
"basic_title_text_id": "Puzzle_QuestSelect_0102",
"puzzle_chara_id": 3208,
"chara_id": 2703,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2",
"": "3"
}
},
{
"id": 1,
"basic_title_text_id": "Puzzle_QuestSelect_0101",
"puzzle_chara_id": 600050,
"chara_id": 600050,
"sort_type": 2,
"difficulty_name_list": {
"Beginner": "0",
"Experienced": "1",
"Expert": "2"
}
}
]

View File

@@ -0,0 +1,230 @@
[
{
"id": 1,
"mission_name": "Clear all Dragoncraft and Portalcraft puzzles puzzles in the Special Round",
"achieved_message": "Mission achieved",
"require_number": 2,
"campaign_commence_time": 1725670800,
"order_id": 5,
"reward_type": 4,
"reward_detail_id": 90001,
"reward_number": 1,
"target_puzzle_group_id": null
},
{
"id": 2,
"mission_name": "Clear all Forestcraft, Shadowcraft and Bloodcraft puzzles in the Special Round",
"achieved_message": "Mission achieved",
"require_number": 3,
"campaign_commence_time": 1722646800,
"order_id": 4,
"reward_type": 4,
"reward_detail_id": 90001,
"reward_number": 1,
"target_puzzle_group_id": null
},
{
"id": 3,
"mission_name": "Clear all Swordcraft, Runecraft and Havencraft puzzles in the Special Round",
"achieved_message": "Mission achieved",
"require_number": 3,
"campaign_commence_time": 1720227600,
"order_id": 3,
"reward_type": 4,
"reward_detail_id": 90001,
"reward_number": 1,
"target_puzzle_group_id": null
},
{
"id": 4,
"mission_name": "Clear all Special Round puzzles",
"achieved_message": "Mission achieved",
"require_number": 8,
"campaign_commence_time": 1720227600,
"order_id": 2,
"reward_type": 7,
"reward_detail_id": 400004315,
"reward_number": 1,
"target_puzzle_group_id": null
},
{
"id": 5,
"mission_name": "Clear all Round 15 puzzles",
"achieved_message": "Cleared all Round 15 puzzles",
"require_number": 3,
"campaign_commence_time": 1716598800,
"order_id": 1,
"reward_type": 7,
"reward_detail_id": 400004314,
"reward_number": 1,
"target_puzzle_group_id": 315
},
{
"id": 6,
"mission_name": "Clear all Round 14 puzzles",
"achieved_message": "Cleared all Round 14 puzzles",
"require_number": 3,
"campaign_commence_time": 1711760400,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3065004,
"reward_number": 1,
"target_puzzle_group_id": 314
},
{
"id": 7,
"mission_name": "Clear all Round 13 puzzles",
"achieved_message": "Cleared all Round 13 puzzles",
"require_number": 3,
"campaign_commence_time": 1708736400,
"order_id": 1,
"reward_type": 7,
"reward_detail_id": 400004313,
"reward_number": 1,
"target_puzzle_group_id": 313
},
{
"id": 8,
"mission_name": "Clear all Round 12 puzzles",
"achieved_message": "Cleared all Round 12 puzzles",
"require_number": 3,
"campaign_commence_time": 1703898000,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074009,
"reward_number": 1,
"target_puzzle_group_id": 312
},
{
"id": 9,
"mission_name": "Clear all Round 11 puzzles",
"achieved_message": "Cleared all Round 11 puzzles",
"require_number": 3,
"campaign_commence_time": 1700269200,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074008,
"reward_number": 1,
"target_puzzle_group_id": 311
},
{
"id": 10,
"mission_name": "Clear all Round 10 puzzles",
"achieved_message": "Cleared all Round 10 puzzles",
"require_number": 3,
"campaign_commence_time": 1692406800,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074007,
"reward_number": 1,
"target_puzzle_group_id": 310
},
{
"id": 11,
"mission_name": "Clear all Round 9 puzzles",
"achieved_message": "Cleared all Round 9 puzzles",
"require_number": 3,
"campaign_commence_time": 1688173200,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074006,
"reward_number": 1,
"target_puzzle_group_id": 309
},
{
"id": 12,
"mission_name": "Clear all Round 8 puzzles",
"achieved_message": "Cleared all Round 8 puzzles",
"require_number": 3,
"campaign_commence_time": 1684544400,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074005,
"reward_number": 1,
"target_puzzle_group_id": 308
},
{
"id": 13,
"mission_name": "Clear all Round 7 puzzles",
"achieved_message": "Cleared all Round 7 puzzles",
"require_number": 3,
"campaign_commence_time": 1677286800,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074004,
"reward_number": 1,
"target_puzzle_group_id": 307
},
{
"id": 14,
"mission_name": "Clear all Round 6 puzzles",
"achieved_message": "Cleared all Round 6 puzzles",
"require_number": 3,
"campaign_commence_time": 1672448400,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074003,
"reward_number": 1,
"target_puzzle_group_id": 306
},
{
"id": 15,
"mission_name": "Clear all Round 5 puzzles",
"achieved_message": "Cleared all Round 5 puzzles",
"require_number": 3,
"campaign_commence_time": 1669424400,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074002,
"reward_number": 1,
"target_puzzle_group_id": 305
},
{
"id": 16,
"mission_name": "Clear all Round 4 puzzles",
"achieved_message": "Cleared all Round 4 puzzles",
"require_number": 3,
"campaign_commence_time": 1660959000,
"order_id": 1,
"reward_type": 6,
"reward_detail_id": 3074001,
"reward_number": 1,
"target_puzzle_group_id": 304
},
{
"id": 17,
"mission_name": "Clear all Round 3 puzzles",
"achieved_message": "Cleared all Round 3 puzzles",
"require_number": 3,
"campaign_commence_time": 1656725400,
"order_id": 1,
"reward_type": 7,
"reward_detail_id": 400004105,
"reward_number": 1,
"target_puzzle_group_id": 303
},
{
"id": 18,
"mission_name": "Clear all Round 2 puzzles",
"achieved_message": "Cleared all Round 2 puzzles",
"require_number": 3,
"campaign_commence_time": 1653096600,
"order_id": 1,
"reward_type": 7,
"reward_detail_id": 400004104,
"reward_number": 1,
"target_puzzle_group_id": 302
},
{
"id": 19,
"mission_name": "Clear all Round 1 puzzles",
"achieved_message": "Cleared all Round 1 puzzles",
"require_number": 3,
"campaign_commence_time": 1651282200,
"order_id": 1,
"reward_type": 10,
"reward_detail_id": 3704,
"reward_number": 1,
"target_puzzle_group_id": 301
}
]

View File

@@ -0,0 +1,906 @@
[
{
"id": 106,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 107,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 108,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 109,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": true,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 110,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": true,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 111,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": true,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 112,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": true,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 113,
"group_id": 316,
"puzzle_difficulty": 2,
"is_additional": true,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 103,
"group_id": 315,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 104,
"group_id": 315,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 105,
"group_id": 315,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 100,
"group_id": 314,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 101,
"group_id": 314,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 102,
"group_id": 314,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 97,
"group_id": 313,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 98,
"group_id": 313,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 99,
"group_id": 313,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 94,
"group_id": 312,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 95,
"group_id": 312,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 96,
"group_id": 312,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 91,
"group_id": 311,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 92,
"group_id": 311,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 93,
"group_id": 311,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 84,
"group_id": 310,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 85,
"group_id": 310,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 86,
"group_id": 310,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 77,
"group_id": 309,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 78,
"group_id": 309,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 79,
"group_id": 309,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 74,
"group_id": 308,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 75,
"group_id": 308,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 76,
"group_id": 308,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 67,
"group_id": 307,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 68,
"group_id": 307,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 69,
"group_id": 307,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 64,
"group_id": 306,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 65,
"group_id": 306,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 66,
"group_id": 306,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 61,
"group_id": 305,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 62,
"group_id": 305,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 63,
"group_id": 305,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 46,
"group_id": 304,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 47,
"group_id": 304,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 48,
"group_id": 304,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 43,
"group_id": 303,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 44,
"group_id": 303,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 45,
"group_id": 303,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 40,
"group_id": 302,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 41,
"group_id": 302,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 42,
"group_id": 302,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 37,
"group_id": 301,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 38,
"group_id": 301,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 39,
"group_id": 301,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 87,
"group_id": 9,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 88,
"group_id": 9,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 89,
"group_id": 9,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 90,
"group_id": 9,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 80,
"group_id": 8,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 81,
"group_id": 8,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 82,
"group_id": 8,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 83,
"group_id": 8,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 70,
"group_id": 7,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 71,
"group_id": 7,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 72,
"group_id": 7,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 73,
"group_id": 7,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 52,
"group_id": 6,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 53,
"group_id": 6,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 54,
"group_id": 6,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 55,
"group_id": 6,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 56,
"group_id": 6,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 57,
"group_id": 6,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 58,
"group_id": 6,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 59,
"group_id": 6,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 60,
"group_id": 6,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 49,
"group_id": 5,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 50,
"group_id": 5,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 51,
"group_id": 5,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 28,
"group_id": 4,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 29,
"group_id": 4,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 30,
"group_id": 4,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 31,
"group_id": 4,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 32,
"group_id": 4,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 33,
"group_id": 4,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 34,
"group_id": 4,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 35,
"group_id": 4,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 36,
"group_id": 4,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 20,
"group_id": 3,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 21,
"group_id": 3,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 22,
"group_id": 3,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 23,
"group_id": 3,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 24,
"group_id": 3,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 25,
"group_id": 3,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 26,
"group_id": 3,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 27,
"group_id": 3,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 10,
"group_id": 2,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 11,
"group_id": 2,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 12,
"group_id": 2,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 13,
"group_id": 2,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 14,
"group_id": 2,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 15,
"group_id": 2,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 16,
"group_id": 2,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 17,
"group_id": 2,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": "Puzzle_Unlock_Condition_0001"
},
{
"id": 18,
"group_id": 2,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": "Puzzle_Unlock_Condition_0001"
},
{
"id": 19,
"group_id": 2,
"puzzle_difficulty": 3,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": "Puzzle_Unlock_Condition_0001"
},
{
"id": 1,
"group_id": 1,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 2,
"group_id": 1,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 3,
"group_id": 1,
"puzzle_difficulty": 0,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 4,
"group_id": 1,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 5,
"group_id": 1,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 6,
"group_id": 1,
"puzzle_difficulty": 1,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 7,
"group_id": 1,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 8,
"group_id": 1,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
},
{
"id": 9,
"group_id": 1,
"puzzle_difficulty": 2,
"is_additional": false,
"is_playable": true,
"release_condition_text_id": ""
}
]

View File

@@ -0,0 +1,164 @@
[
{
"card_id": 100114010
},
{
"card_id": 100211010
},
{
"card_id": 100214010
},
{
"card_id": 100214020
},
{
"card_id": 100314010
},
{
"card_id": 100314020
},
{
"card_id": 100314030
},
{
"card_id": 100314040
},
{
"card_id": 100314070
},
{
"card_id": 100414010
},
{
"card_id": 100514010
},
{
"card_id": 100614010
},
{
"card_id": 100614020
},
{
"card_id": 100714010
},
{
"card_id": 100714020
},
{
"card_id": 100814010
},
{
"card_id": 101114010
},
{
"card_id": 105312010
},
{
"card_id": 106114010
},
{
"card_id": 107322010
},
{
"card_id": 111124010
},
{
"card_id": 111214010
},
{
"card_id": 111314010
},
{
"card_id": 111434010
},
{
"card_id": 111514010
},
{
"card_id": 111634010
},
{
"card_id": 111734010
},
{
"card_id": 111814010
},
{
"card_id": 112031010
},
{
"card_id": 112122010
},
{
"card_id": 112222010
},
{
"card_id": 112224010
},
{
"card_id": 112322010
},
{
"card_id": 112414010
},
{
"card_id": 112422010
},
{
"card_id": 112522010
},
{
"card_id": 112622010
},
{
"card_id": 112722010
},
{
"card_id": 112822010
},
{
"card_id": 112834010
},
{
"card_id": 113034010
},
{
"card_id": 113114010
},
{
"card_id": 113224010
},
{
"card_id": 113614010
},
{
"card_id": 113733010
},
{
"card_id": 113824010
},
{
"card_id": 114014010
},
{
"card_id": 114031010
},
{
"card_id": 114234010
},
{
"card_id": 114834010
},
{
"card_id": 115124010
},
{
"card_id": 115214010
},
{
"card_id": 115414010
},
{
"card_id": 115814010
}
]

View File

@@ -0,0 +1,14 @@
{
"ts_rotation_id": "10015",
"is_battle_pass_period": true,
"is_beginner_mission": false,
"card_set_id_for_resource_dl_view": 1,
"rotation_card_set_ids": [
10000,
10011,
10012,
10013,
10014,
10015
]
}

View File

@@ -0,0 +1,21 @@
{
"id": 1,
"enable": 1,
"crystal_cost": 600,
"rupy_cost": 600,
"ticket_cost": 4,
"deck_using_num_min": 30,
"schedule_id": 21,
"is_join": false,
"is_deck_code_maintenance": false,
"pack_info": [
10032,
10032,
10031,
10030,
10029
],
"sales_period_info": {
"sales_period_series": 33
}
}

View File

@@ -0,0 +1,7 @@
[
{
"id": 1,
"deck_format": "5",
"end_time": "2030-06-26 19:59:59"
}
]

View File

@@ -0,0 +1,958 @@
[
{
"card_id": 101041010,
"cost": 1
},
{
"card_id": 101041020,
"cost": 1
},
{
"card_id": 101041030,
"cost": 1
},
{
"card_id": 101141010,
"cost": 1
},
{
"card_id": 101141020,
"cost": 1
},
{
"card_id": 101141030,
"cost": 1
},
{
"card_id": 101241010,
"cost": 1
},
{
"card_id": 101241020,
"cost": 1
},
{
"card_id": 101241030,
"cost": 1
},
{
"card_id": 101341010,
"cost": 1
},
{
"card_id": 101341020,
"cost": 1
},
{
"card_id": 101341030,
"cost": 1
},
{
"card_id": 101441010,
"cost": 1
},
{
"card_id": 101441020,
"cost": 1
},
{
"card_id": 101441030,
"cost": 1
},
{
"card_id": 101541010,
"cost": 1
},
{
"card_id": 101541020,
"cost": 1
},
{
"card_id": 101541030,
"cost": 1
},
{
"card_id": 101641010,
"cost": 1
},
{
"card_id": 101641020,
"cost": 1
},
{
"card_id": 101641030,
"cost": 1
},
{
"card_id": 101741010,
"cost": 1
},
{
"card_id": 101741020,
"cost": 1
},
{
"card_id": 101741030,
"cost": 1
},
{
"card_id": 102041010,
"cost": 1
},
{
"card_id": 102041020,
"cost": 1
},
{
"card_id": 102141010,
"cost": 1
},
{
"card_id": 102241010,
"cost": 1
},
{
"card_id": 102341010,
"cost": 1
},
{
"card_id": 102442010,
"cost": 1
},
{
"card_id": 102541010,
"cost": 1
},
{
"card_id": 102641010,
"cost": 1
},
{
"card_id": 102743010,
"cost": 1
},
{
"card_id": 103041010,
"cost": 1
},
{
"card_id": 103041020,
"cost": 1
},
{
"card_id": 103141010,
"cost": 1
},
{
"card_id": 103241010,
"cost": 1
},
{
"card_id": 103341010,
"cost": 1
},
{
"card_id": 103441010,
"cost": 1
},
{
"card_id": 103541010,
"cost": 1
},
{
"card_id": 103641010,
"cost": 1
},
{
"card_id": 103741010,
"cost": 1
},
{
"card_id": 104041010,
"cost": 1
},
{
"card_id": 104041020,
"cost": 1
},
{
"card_id": 104141010,
"cost": 1
},
{
"card_id": 104141020,
"cost": 1
},
{
"card_id": 104241010,
"cost": 1
},
{
"card_id": 104241020,
"cost": 1
},
{
"card_id": 104341010,
"cost": 1
},
{
"card_id": 104341020,
"cost": 1
},
{
"card_id": 104441010,
"cost": 1
},
{
"card_id": 104441020,
"cost": 1
},
{
"card_id": 104541010,
"cost": 1
},
{
"card_id": 104541020,
"cost": 1
},
{
"card_id": 104641010,
"cost": 1
},
{
"card_id": 104641020,
"cost": 1
},
{
"card_id": 104741010,
"cost": 1
},
{
"card_id": 104741020,
"cost": 1
},
{
"card_id": 105041010,
"cost": 1
},
{
"card_id": 105041020,
"cost": 1
},
{
"card_id": 105141010,
"cost": 1
},
{
"card_id": 105141020,
"cost": 1
},
{
"card_id": 105241010,
"cost": 1
},
{
"card_id": 105241020,
"cost": 1
},
{
"card_id": 105341010,
"cost": 1
},
{
"card_id": 105341020,
"cost": 1
},
{
"card_id": 105441010,
"cost": 1
},
{
"card_id": 105441020,
"cost": 1
},
{
"card_id": 105541010,
"cost": 1
},
{
"card_id": 105541020,
"cost": 1
},
{
"card_id": 105641010,
"cost": 1
},
{
"card_id": 105641020,
"cost": 1
},
{
"card_id": 105741010,
"cost": 1
},
{
"card_id": 105741020,
"cost": 1
},
{
"card_id": 106041010,
"cost": 1
},
{
"card_id": 106041020,
"cost": 1
},
{
"card_id": 106141010,
"cost": 1
},
{
"card_id": 106141020,
"cost": 1
},
{
"card_id": 106241010,
"cost": 1
},
{
"card_id": 106241020,
"cost": 1
},
{
"card_id": 106341010,
"cost": 1
},
{
"card_id": 106341020,
"cost": 1
},
{
"card_id": 106441010,
"cost": 1
},
{
"card_id": 106441020,
"cost": 1
},
{
"card_id": 106541010,
"cost": 1
},
{
"card_id": 106541020,
"cost": 1
},
{
"card_id": 106641010,
"cost": 1
},
{
"card_id": 106641020,
"cost": 1
},
{
"card_id": 106741010,
"cost": 1
},
{
"card_id": 106741020,
"cost": 1
},
{
"card_id": 107041010,
"cost": 1
},
{
"card_id": 107041020,
"cost": 1
},
{
"card_id": 107141010,
"cost": 1
},
{
"card_id": 107141020,
"cost": 1
},
{
"card_id": 107241010,
"cost": 1
},
{
"card_id": 107241020,
"cost": 1
},
{
"card_id": 107341010,
"cost": 1
},
{
"card_id": 107341020,
"cost": 1
},
{
"card_id": 107441010,
"cost": 1
},
{
"card_id": 107441020,
"cost": 1
},
{
"card_id": 107541010,
"cost": 1
},
{
"card_id": 107541020,
"cost": 1
},
{
"card_id": 107641010,
"cost": 1
},
{
"card_id": 107641020,
"cost": 1
},
{
"card_id": 107741010,
"cost": 1
},
{
"card_id": 107741020,
"cost": 1
},
{
"card_id": 107841010,
"cost": 1
},
{
"card_id": 107841020,
"cost": 1
},
{
"card_id": 107841030,
"cost": 1
},
{
"card_id": 108041010,
"cost": 1
},
{
"card_id": 108044010,
"cost": 1
},
{
"card_id": 108141010,
"cost": 1
},
{
"card_id": 108141020,
"cost": 1
},
{
"card_id": 108141030,
"cost": 1
},
{
"card_id": 108241010,
"cost": 1
},
{
"card_id": 108241020,
"cost": 1
},
{
"card_id": 108241030,
"cost": 1
},
{
"card_id": 108341010,
"cost": 1
},
{
"card_id": 108341020,
"cost": 1
},
{
"card_id": 108341030,
"cost": 1
},
{
"card_id": 108441010,
"cost": 1
},
{
"card_id": 108441020,
"cost": 1
},
{
"card_id": 108441030,
"cost": 1
},
{
"card_id": 108541010,
"cost": 1
},
{
"card_id": 108541020,
"cost": 1
},
{
"card_id": 108541030,
"cost": 1
},
{
"card_id": 108641010,
"cost": 1
},
{
"card_id": 108641020,
"cost": 1
},
{
"card_id": 108641030,
"cost": 1
},
{
"card_id": 108741010,
"cost": 1
},
{
"card_id": 108741020,
"cost": 1
},
{
"card_id": 108741030,
"cost": 1
},
{
"card_id": 108841010,
"cost": 1
},
{
"card_id": 108841020,
"cost": 1
},
{
"card_id": 108841030,
"cost": 1
},
{
"card_id": 109041010,
"cost": 1
},
{
"card_id": 109041020,
"cost": 1
},
{
"card_id": 109141010,
"cost": 1
},
{
"card_id": 109141020,
"cost": 1
},
{
"card_id": 109141030,
"cost": 1
},
{
"card_id": 109241010,
"cost": 1
},
{
"card_id": 109241020,
"cost": 1
},
{
"card_id": 109241030,
"cost": 1
},
{
"card_id": 109341010,
"cost": 1
},
{
"card_id": 109341020,
"cost": 1
},
{
"card_id": 109341030,
"cost": 1
},
{
"card_id": 109441010,
"cost": 1
},
{
"card_id": 109441020,
"cost": 1
},
{
"card_id": 109441030,
"cost": 1
},
{
"card_id": 109541010,
"cost": 1
},
{
"card_id": 109541020,
"cost": 1
},
{
"card_id": 109541030,
"cost": 1
},
{
"card_id": 109641010,
"cost": 1
},
{
"card_id": 109641020,
"cost": 1
},
{
"card_id": 109641030,
"cost": 1
},
{
"card_id": 109741010,
"cost": 1
},
{
"card_id": 109741020,
"cost": 1
},
{
"card_id": 109741030,
"cost": 1
},
{
"card_id": 109841010,
"cost": 1
},
{
"card_id": 109841020,
"cost": 1
},
{
"card_id": 109841030,
"cost": 1
},
{
"card_id": 110041010,
"cost": 1
},
{
"card_id": 110041020,
"cost": 1
},
{
"card_id": 110141010,
"cost": 1
},
{
"card_id": 110141020,
"cost": 1
},
{
"card_id": 110141030,
"cost": 1
},
{
"card_id": 110241010,
"cost": 1
},
{
"card_id": 110241020,
"cost": 1
},
{
"card_id": 110241030,
"cost": 1
},
{
"card_id": 110341010,
"cost": 1
},
{
"card_id": 110341020,
"cost": 1
},
{
"card_id": 110341030,
"cost": 1
},
{
"card_id": 110441010,
"cost": 1
},
{
"card_id": 110441020,
"cost": 1
},
{
"card_id": 110441030,
"cost": 1
},
{
"card_id": 110541010,
"cost": 1
},
{
"card_id": 110541020,
"cost": 1
},
{
"card_id": 110541030,
"cost": 1
},
{
"card_id": 110641010,
"cost": 1
},
{
"card_id": 110641020,
"cost": 1
},
{
"card_id": 110641030,
"cost": 1
},
{
"card_id": 110741010,
"cost": 1
},
{
"card_id": 110741020,
"cost": 1
},
{
"card_id": 110741030,
"cost": 1
},
{
"card_id": 110841010,
"cost": 1
},
{
"card_id": 110841020,
"cost": 1
},
{
"card_id": 110841030,
"cost": 1
},
{
"card_id": 111041010,
"cost": 1
},
{
"card_id": 111041020,
"cost": 1
},
{
"card_id": 111141010,
"cost": 1
},
{
"card_id": 111141020,
"cost": 1
},
{
"card_id": 111141030,
"cost": 1
},
{
"card_id": 111241010,
"cost": 1
},
{
"card_id": 111241020,
"cost": 1
},
{
"card_id": 111241030,
"cost": 1
},
{
"card_id": 111341010,
"cost": 1
},
{
"card_id": 111341020,
"cost": 1
},
{
"card_id": 111341030,
"cost": 1
},
{
"card_id": 111441010,
"cost": 1
},
{
"card_id": 111441020,
"cost": 1
},
{
"card_id": 111441030,
"cost": 1
},
{
"card_id": 111541010,
"cost": 1
},
{
"card_id": 111541020,
"cost": 1
},
{
"card_id": 111541030,
"cost": 1
},
{
"card_id": 111641010,
"cost": 1
},
{
"card_id": 111641020,
"cost": 1
},
{
"card_id": 111641030,
"cost": 1
},
{
"card_id": 111741010,
"cost": 1
},
{
"card_id": 111741020,
"cost": 1
},
{
"card_id": 111741030,
"cost": 1
},
{
"card_id": 111841010,
"cost": 1
},
{
"card_id": 111841020,
"cost": 1
},
{
"card_id": 111843010,
"cost": 1
},
{
"card_id": 112041010,
"cost": 1
},
{
"card_id": 112041020,
"cost": 1
},
{
"card_id": 112141010,
"cost": 1
},
{
"card_id": 112141020,
"cost": 1
},
{
"card_id": 112141030,
"cost": 1
},
{
"card_id": 112241010,
"cost": 1
},
{
"card_id": 112241020,
"cost": 1
},
{
"card_id": 112241030,
"cost": 1
},
{
"card_id": 112341010,
"cost": 1
},
{
"card_id": 112341020,
"cost": 1
},
{
"card_id": 112341030,
"cost": 1
},
{
"card_id": 112441010,
"cost": 1
},
{
"card_id": 112441020,
"cost": 1
},
{
"card_id": 112441030,
"cost": 1
},
{
"card_id": 112541010,
"cost": 1
},
{
"card_id": 112541020,
"cost": 1
},
{
"card_id": 112541030,
"cost": 1
},
{
"card_id": 112641010,
"cost": 1
},
{
"card_id": 112641020,
"cost": 1
},
{
"card_id": 112641030,
"cost": 1
},
{
"card_id": 112741010,
"cost": 1
},
{
"card_id": 112741020,
"cost": 1
},
{
"card_id": 112741030,
"cost": 1
},
{
"card_id": 112841010,
"cost": 1
},
{
"card_id": 112841020,
"cost": 1
},
{
"card_id": 112841030,
"cost": 1
}
]

View File

@@ -0,0 +1,14 @@
[
{
"card_id": 112834010,
"restriction_value": 0
},
{
"card_id": 109623010,
"restriction_value": 0
},
{
"card_id": 107813030,
"restriction_value": 1
}
]

View File

@@ -1,48 +0,0 @@
{
"data_headers": { "sid": "fixture", "short_udid": 1, "viewer_id": 1, "servertime": 1779591187, "result_code": 1 },
"data": {
"pack_config_list": [
{
"parent_gacha_id": 10001, "base_pack_id": 10001, "override_draw_effect_pack_id": 10001,
"override_ui_effect_pack_id": 10001, "gacha_type": 1, "sleeve_id": 3000011, "special_sleeve_id": 0,
"commence_date": "2015-04-01 00:00:00", "complete_date": "2030-12-31 23:59:59",
"cardpack_banner_list": [], "gacha_detail": "A pack contains 8 cards, including at least one silver, gold, or legendary card.",
"child_gacha_info": [
{ "gacha_id": 100002, "type_detail": 2, "cost": 100, "count": 8, "override_increase_gacha_point": "1" },
{ "gacha_id": 200001, "type_detail": 3, "cost": 50, "count": 8, "override_increase_gacha_point": "1", "is_daily_single": true },
{ "gacha_id": 400002, "type_detail": 7, "cost": 100, "count": 8, "override_increase_gacha_point": "1" }
],
"open_count": 0, "open_count_limit": 0, "is_hide": 0, "pack_category": 0,
"gacha_point": { "pack_id": "10001", "gacha_point": 0, "increase_gacha_point": "1", "exchangeable_gacha_point": 400, "is_exchangeable_gacha_point": false },
"is_pre_release": false, "exists_purchase_reward": false, "is_new": false, "sales_period_info": [], "poster_type": 0
},
{
"parent_gacha_id": 92001, "base_pack_id": 90001, "override_draw_effect_pack_id": 90001,
"override_ui_effect_pack_id": 90001, "gacha_type": 1, "sleeve_id": 5090001, "special_sleeve_id": 0,
"commence_date": "2017-06-14 10:00:00", "complete_date": "2030-12-31 23:59:59",
"cardpack_banner_list": [], "gacha_detail": "A pack contains 8 cards, including at least one leader card!",
"child_gacha_info": [
{ "gacha_id": 920002, "type_detail": 5, "cost": 1, "count": 8, "item_id": "92001", "item_number": 0 }
],
"open_count": 0, "open_count_limit": 0, "is_hide": 1, "pack_category": 1, "gacha_point": null,
"is_pre_release": false, "exists_purchase_reward": false, "is_new": false, "sales_period_info": [], "poster_type": 0
},
{
"parent_gacha_id": 16015, "base_pack_id": 10015, "override_draw_effect_pack_id": 10015,
"override_ui_effect_pack_id": 10015, "gacha_type": 1, "sleeve_id": 5010015, "special_sleeve_id": 0,
"commence_date": "2017-07-01 03:00:00", "complete_date": "2030-12-31 23:59:59",
"cardpack_banner_list": [
{ "banner_name": "card_pack_711331010_dialog", "dialog_title": "Dia_BuyCard_005_Title" }
],
"gacha_detail": "A pack contains 8 cards, including at least one silver, gold, or legendary card.",
"child_gacha_info": [
{ "gacha_id": 160152, "type_detail": 2, "cost": 100, "count": 8, "override_increase_gacha_point": "1" },
{ "gacha_id": 460152, "type_detail": 7, "cost": 100, "count": 8, "override_increase_gacha_point": "1" }
],
"open_count": 0, "open_count_limit": 0, "is_hide": 0, "pack_category": 0,
"gacha_point": { "pack_id": "10015", "gacha_point": 0, "increase_gacha_point": "1", "exchangeable_gacha_point": 400, "is_exchangeable_gacha_point": false },
"is_pre_release": false, "exists_purchase_reward": false, "is_new": false, "sales_period_info": [], "poster_type": 0
}
]
}
}

View File

@@ -0,0 +1,154 @@
[
{
"parent_gacha_id": 10001,
"base_pack_id": 10001,
"gacha_type": 1,
"pack_category": 0,
"poster_type": 0,
"commence_date": "2015-04-01 00:00:00",
"complete_date": "2030-12-31 23:59:59",
"sleeve_id": 3000011,
"special_sleeve_id": 0,
"override_draw_effect_pack_id": 10001,
"override_ui_effect_pack_id": 10001,
"gacha_detail": "A pack contains 8 cards, including at least one silver, gold, or legendary card.",
"is_hide": false,
"is_new": false,
"is_pre_release": false,
"open_count_limit": 0,
"sales_period_time": null,
"gacha_point": {
"exchangeable_point": 400,
"increase_gacha_point": 1
},
"child_gachas": [
{
"gacha_id": 100002,
"type_detail": 2,
"cost": 100,
"card_count": 8,
"item_id": null,
"is_daily_single": false,
"override_increase_gacha_point": 1,
"purchase_limit_count": 0,
"free_gacha_campaign_id": null,
"campaign_name": null
},
{
"gacha_id": 200001,
"type_detail": 3,
"cost": 50,
"card_count": 8,
"item_id": null,
"is_daily_single": true,
"override_increase_gacha_point": 1,
"purchase_limit_count": 0,
"free_gacha_campaign_id": null,
"campaign_name": null
},
{
"gacha_id": 400002,
"type_detail": 7,
"cost": 100,
"card_count": 8,
"item_id": null,
"is_daily_single": false,
"override_increase_gacha_point": 1,
"purchase_limit_count": 0,
"free_gacha_campaign_id": null,
"campaign_name": null
}
],
"banners": []
},
{
"parent_gacha_id": 92001,
"base_pack_id": 90001,
"gacha_type": 1,
"pack_category": 1,
"poster_type": 0,
"commence_date": "2017-06-14 10:00:00",
"complete_date": "2030-12-31 23:59:59",
"sleeve_id": 5090001,
"special_sleeve_id": 0,
"override_draw_effect_pack_id": 90001,
"override_ui_effect_pack_id": 90001,
"gacha_detail": "A pack contains 8 cards, including at least one leader card!",
"is_hide": true,
"is_new": false,
"is_pre_release": false,
"open_count_limit": 0,
"sales_period_time": null,
"gacha_point": null,
"child_gachas": [
{
"gacha_id": 920002,
"type_detail": 5,
"cost": 1,
"card_count": 8,
"item_id": 92001,
"is_daily_single": false,
"override_increase_gacha_point": 0,
"purchase_limit_count": 0,
"free_gacha_campaign_id": null,
"campaign_name": null
}
],
"banners": []
},
{
"parent_gacha_id": 16015,
"base_pack_id": 10015,
"gacha_type": 1,
"pack_category": 0,
"poster_type": 0,
"commence_date": "2017-07-01 03:00:00",
"complete_date": "2030-12-31 23:59:59",
"sleeve_id": 5010015,
"special_sleeve_id": 0,
"override_draw_effect_pack_id": 10015,
"override_ui_effect_pack_id": 10015,
"gacha_detail": "A pack contains 8 cards, including at least one silver, gold, or legendary card.",
"is_hide": false,
"is_new": false,
"is_pre_release": false,
"open_count_limit": 0,
"sales_period_time": null,
"gacha_point": {
"exchangeable_point": 400,
"increase_gacha_point": 1
},
"child_gachas": [
{
"gacha_id": 160152,
"type_detail": 2,
"cost": 100,
"card_count": 8,
"item_id": null,
"is_daily_single": false,
"override_increase_gacha_point": 1,
"purchase_limit_count": 0,
"free_gacha_campaign_id": null,
"campaign_name": null
},
{
"gacha_id": 460152,
"type_detail": 7,
"cost": 100,
"card_count": 8,
"item_id": null,
"is_daily_single": false,
"override_increase_gacha_point": 1,
"purchase_limit_count": 0,
"free_gacha_campaign_id": null,
"campaign_name": null
}
],
"banners": [
{
"banner_name": "card_pack_711331010_dialog",
"dialog_title": "Dia_BuyCard_005_Title"
}
]
}
]

View 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;
}
}

View 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;
}
}

View File

@@ -0,0 +1,38 @@
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>.
/// Curve is global; rows missing from the seed are LEFT INTACT.
/// </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)
{
Console.WriteLine("[BattlePassImporter] No seed rows; skipping.");
return 0;
}
var existing = await context.BattlePassLevels.ToDictionaryAsync(e => e.Level);
int created = 0, updated = 0;
foreach (var s in seed)
{
if (s.Level == 0) continue;
var entry = existing.TryGetValue(s.Level, out var ex) ? ex : new BattlePassLevelEntry { Level = s.Level };
entry.RequiredPoint = s.RequiredPoint;
if (ex is null) { context.BattlePassLevels.Add(entry); existing[s.Level] = entry; created++; }
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[BattlePassImporter] +{created}/~{updated}");
return created + updated;
}
}

View File

@@ -0,0 +1,90 @@
using Microsoft.EntityFrameworkCore;
using SVSim.Bootstrap.Models.Seed;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Authoritative upsert of battle-pass rewards from <c>seeds/battle-pass-rewards.json</c>.
/// For each (season_id, track, level) row in the seed: upsert. For rows in the DB that match
/// a seed-mentioned season but are NOT in the seed: DELETE (seed is authoritative per season).
/// Rewards for seasons not mentioned in the seed are left untouched.
/// </summary>
public class BattlePassRewardImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<BattlePassRewardSeed>(Path.Combine(seedDir, "battle-pass-rewards.json"));
if (seed.Count == 0)
{
Console.WriteLine("[BattlePassRewardImporter] No seed rows; skipping.");
return 0;
}
var seededSeasonIds = seed.Select(s => s.SeasonId).Distinct().ToHashSet();
var dbRows = await context.BattlePassRewards
.Where(r => seededSeasonIds.Contains(r.SeasonId))
.ToListAsync();
var dbByKey = dbRows.ToDictionary(r => (r.SeasonId, r.Track, r.Level));
int created = 0, updated = 0;
var seenKeys = new HashSet<(int, BattlePassTrack, int)>();
foreach (var s in seed)
{
var track = ParseTrack(s.Track);
var key = (s.SeasonId, track, s.Level);
seenKeys.Add(key);
if (dbByKey.TryGetValue(key, out var ex))
{
ex.RewardType = s.RewardType;
ex.RewardDetailId = s.RewardDetailId;
ex.RewardNumber = s.RewardNumber;
ex.IsAppealExclusion = s.IsAppealExclusion;
updated++;
}
else
{
context.BattlePassRewards.Add(new BattlePassRewardEntry
{
Id = MakeId(s.SeasonId, track, s.Level),
SeasonId = s.SeasonId, Track = track, Level = s.Level,
RewardType = s.RewardType, RewardDetailId = s.RewardDetailId,
RewardNumber = s.RewardNumber, IsAppealExclusion = s.IsAppealExclusion,
});
created++;
}
}
// Authoritative deletion within seeded seasons.
int deleted = 0;
foreach (var row in dbRows)
{
if (!seenKeys.Contains((row.SeasonId, row.Track, row.Level)))
{
context.BattlePassRewards.Remove(row);
deleted++;
}
}
await context.SaveChangesAsync();
Console.WriteLine($"[BattlePassRewardImporter] +{created}/~{updated}/-{deleted}");
return created + updated;
}
private static BattlePassTrack ParseTrack(string s) => s.ToLowerInvariant() switch
{
"normal" => BattlePassTrack.Normal,
"premium" => BattlePassTrack.Premium,
_ => throw new InvalidOperationException($"unknown battle pass track: {s}"),
};
/// <summary>
/// Derives a stable surrogate PK from the (SeasonId, Track, Level) natural key.
/// Encoding: season * 10_000 + track * 1_000 + level.
/// Safe for season &lt; 10_000, track ∈ {0,1}, level &lt; 1_000 — all realistic values.
/// </summary>
private static long MakeId(int seasonId, BattlePassTrack track, int level) =>
(long)seasonId * 10_000L + (int)track * 1_000 + level;
}

View File

@@ -0,0 +1,48 @@
using System.Globalization;
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 seasons from <c>seeds/battle-pass-seasons.json</c>.
/// Rows missing from the seed are LEFT INTACT (historic seasons).
/// </summary>
public class BattlePassSeasonImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<BattlePassSeasonSeed>(Path.Combine(seedDir, "battle-pass-seasons.json"));
if (seed.Count == 0)
{
Console.WriteLine("[BattlePassSeasonImporter] No seed rows; skipping.");
return 0;
}
var existing = await context.BattlePassSeasons.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 BattlePassSeasonEntry { Id = s.Id };
entry.Name = s.Name;
entry.MaxLevel = s.MaxLevel;
// Postgres 'timestamp with time zone' only accepts UTC offset; JST-offset values
// from the seed are converted to UTC to preserve the instant. Comparisons via
// DateTimeOffset are instant-based, so the JST→UTC conversion is semantically lossless.
entry.StartDate = DateTimeOffset.Parse(s.StartDate, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToUniversalTime();
entry.EndDate = DateTimeOffset.Parse(s.EndDate, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToUniversalTime();
entry.CanPurchase = s.CanPurchase;
entry.PriceCrystal = s.PriceCrystal;
entry.Description = s.Description;
if (ex is null) { context.BattlePassSeasons.Add(entry); existing[s.Id] = entry; created++; }
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[BattlePassSeasonImporter] +{created}/~{updated}");
return created + updated;
}
}

View File

@@ -1,16 +1,16 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SVSim.Bootstrap.Models.Seed;
using SVSim.Database; using SVSim.Database;
using SVSim.Database.Models; using SVSim.Database.Models;
using static SVSim.Bootstrap.Importers.ImporterBase;
namespace SVSim.Bootstrap.Importers; namespace SVSim.Bootstrap.Importers;
/// <summary> /// <summary>
/// Loads the prebuilt-deck catalog from a mix of client-master CSVs and one prod-capture JSON. /// Loads the prebuilt-deck catalog from a mix of client-master CSVs and one seed JSON.
/// Three methods run in dependency order (see Bootstrap/Program.cs): /// Three methods run in dependency order (see Bootstrap/Program.cs):
/// 1. ImportSeriesAsync — build_deck_series_master.csv → 22 series rows (all IsEnabled=false initially) /// 1. ImportSeriesAsync — build_deck_series_master.csv → 22 series rows (all IsEnabled=false initially)
/// 2. ImportCatalogAsync — prod-captures/build_deck-info-*.json → enriches 7 series + 53 products (Task 15) /// 2. ImportCatalogAsync — seeds/build-deck-catalog.json → enriches 7 series + 53 products
/// (tier backfill for missing intro/regular prices is performed by the extractor)
/// 3. ImportPackageAsync — build_deck_package_master.csv → card lists for all 112 products, /// 3. ImportPackageAsync — build_deck_package_master.csv → card lists for all 112 products,
/// creates disabled stubs for products not seeded by the catalog importer /// creates disabled stubs for products not seeded by the catalog importer
/// Idempotent — re-runnable on the same files. /// Idempotent — re-runnable on the same files.
@@ -133,14 +133,13 @@ public class BuildDeckImporter
return created + updated; return created + updated;
} }
public async Task<int> ImportCatalogAsync(SVSimDbContext db, string capturesDir) public async Task<int> ImportCatalogAsync(SVSimDbContext db, string seedDir)
{ {
var data = LoadCapture(capturesDir, "build_deck-info"); var seed = SeedLoader.LoadList<BuildDeckCatalogSeed>(Path.Combine(seedDir, "build-deck-catalog.json"));
if (data is null) return 0; if (seed.Count == 0) return 0;
int touchedSeries = 0, touchedProducts = 0; int touchedSeries = 0, touchedProducts = 0;
// Load existing rows for fast lookup
var existingSeries = await db.BuildDeckSeries var existingSeries = await db.BuildDeckSeries
.Include(s => s.SeriesRewards) .Include(s => s.SeriesRewards)
.ToDictionaryAsync(s => s.Id); .ToDictionaryAsync(s => s.Id);
@@ -148,126 +147,75 @@ public class BuildDeckImporter
.Include(p => p.Rewards) .Include(p => p.Rewards)
.ToDictionaryAsync(p => p.Id); .ToDictionaryAsync(p => p.Id);
// The captured data root is an object keyed by order_id string ("15"…"21"); iterate values. foreach (var s in seed)
foreach (var seriesNode in data.Value.EnumerateObject())
{ {
var s = seriesNode.Value; if (s.SeriesId == 0) continue;
int seriesId = GetInt(s, "series_id");
int orderId = GetInt(s, "order_id");
bool isNew = GetBool(s, "is_new");
if (!existingSeries.TryGetValue(seriesId, out var seriesRow)) if (!existingSeries.TryGetValue(s.SeriesId, out var seriesRow))
{ {
// Catalog runs before package importer in production, so series rows from the series // Catalog typically runs after the series CSV; if a seed series isn't in the
// CSV should already exist. If not (e.g. the capture has a series the CSV doesn't), // CSV we create a bare stub so the FK from products holds.
// create a bare row so the FK from products holds.
seriesRow = new BuildDeckSeriesEntry seriesRow = new BuildDeckSeriesEntry
{ {
Id = seriesId, NameKey = string.Empty, IntroKey = string.Empty, Id = s.SeriesId, NameKey = string.Empty, IntroKey = string.Empty,
TitlePath = string.Empty, DrumrollPath = string.Empty, TitlePath = string.Empty, DrumrollPath = string.Empty,
}; };
db.BuildDeckSeries.Add(seriesRow); db.BuildDeckSeries.Add(seriesRow);
existingSeries[seriesId] = seriesRow; existingSeries[s.SeriesId] = seriesRow;
} }
seriesRow.OrderIndex = orderId; seriesRow.OrderIndex = s.OrderId;
seriesRow.IsNew = isNew; seriesRow.IsNew = s.IsNew;
seriesRow.IsEnabled = true; seriesRow.IsEnabled = true;
// Series rewards: replace wholesale (capture is authoritative for enabled series)
seriesRow.SeriesRewards.Clear(); seriesRow.SeriesRewards.Clear();
if (s.TryGetProperty("series_rewards", out var seriesRewards) && foreach (var r in s.SeriesRewards)
seriesRewards.ValueKind == JsonValueKind.Object)
{ {
foreach (var tier in seriesRewards.EnumerateObject()) seriesRow.SeriesRewards.Add(new BuildDeckSeriesRewardEntry
{ {
if (!int.TryParse(tier.Name, out int tierIndex)) continue; TierIndex = r.TierIndex,
if (!tier.Value.TryGetProperty("reward_list", out var rewardList) || ItemIndex = r.ItemIndex,
rewardList.ValueKind != JsonValueKind.Array) continue; RewardType = r.RewardType,
RewardDetailId = r.RewardDetailId,
int itemIndex = 0; RewardNumber = r.RewardNumber,
foreach (var r in rewardList.EnumerateArray()) MessageId = r.MessageId,
{ });
seriesRow.SeriesRewards.Add(new BuildDeckSeriesRewardEntry
{
TierIndex = tierIndex,
ItemIndex = itemIndex++,
RewardType = GetInt(r, "reward_type"),
RewardDetailId = GetLong(r, "reward_detail_id"),
RewardNumber = GetInt(r, "reward_number"),
MessageId = GetInt(r, "message_id"),
});
}
}
} }
touchedSeries++; touchedSeries++;
// Products foreach (var p in s.Products)
if (!s.TryGetProperty("products", out var products) || products.ValueKind != JsonValueKind.Array)
continue;
// First pass: parse each captured product, track intro/regular tiers per product.
var capturedThisSeries = new List<BuildDeckProductEntry>();
foreach (var p in products.EnumerateArray())
{ {
int productId = GetInt(p, "product_id"); if (!existingProducts.TryGetValue(p.ProductId, out var productRow))
if (!existingProducts.TryGetValue(productId, out var productRow))
{ {
productRow = new BuildDeckProductEntry { Id = productId, SeriesId = seriesId }; productRow = new BuildDeckProductEntry { Id = p.ProductId, SeriesId = s.SeriesId };
db.BuildDeckProducts.Add(productRow); db.BuildDeckProducts.Add(productRow);
existingProducts[productId] = productRow; existingProducts[p.ProductId] = productRow;
} }
productRow.SeriesId = seriesId; productRow.SeriesId = s.SeriesId;
productRow.LeaderId = GetInt(p, "leader_id"); productRow.LeaderId = p.LeaderId;
productRow.DeckCode = GetString(p, "deck_code"); productRow.DeckCode = p.DeckCode;
productRow.ProductNameKey = GetString(p, "product_name"); productRow.ProductNameKey = p.ProductName;
productRow.FeaturedCardId = GetLong(p, "featured_card_id"); productRow.FeaturedCardId = p.FeaturedCardId;
productRow.PurchaseNumMax = GetInt(p, "purchase_num_max"); productRow.PurchaseNumMax = p.PurchaseNumMax;
productRow.IsEnabled = true; productRow.IsEnabled = true;
productRow.IntroPriceCrystal = p.IntroPriceCrystal;
productRow.RegularPriceCrystal = p.RegularPriceCrystal;
productRow.IntroPriceRupy = p.IntroPriceRupy;
productRow.RegularPriceRupy = p.RegularPriceRupy;
bool isFirstPrice = GetBool(p, "is_first_price");
// Tier-aware price ingestion: each captured row has ONE price tier (intro OR regular).
int? priceCrystal = p.TryGetProperty("price_crystal", out var pc) && pc.ValueKind != JsonValueKind.Null
? (int?)GetInt(p, "price_crystal") : null;
int? priceRupy = p.TryGetProperty("price_rupy", out var pr) && pr.ValueKind != JsonValueKind.Null
? (int?)GetInt(p, "price_rupy") : null;
if (priceCrystal is not null)
{
if (isFirstPrice) productRow.IntroPriceCrystal = priceCrystal;
else productRow.RegularPriceCrystal = priceCrystal;
}
if (priceRupy is not null)
{
if (isFirstPrice) productRow.IntroPriceRupy = priceRupy;
else productRow.RegularPriceRupy = priceRupy;
}
// Product rewards: replace wholesale
productRow.Rewards.Clear(); productRow.Rewards.Clear();
if (p.TryGetProperty("rewards", out var rewards) && rewards.ValueKind == JsonValueKind.Object) foreach (var r in p.Rewards)
{ {
foreach (var r in rewards.EnumerateObject()) productRow.Rewards.Add(new BuildDeckProductRewardEntry
{ {
if (!int.TryParse(r.Name, out int idx)) continue; RewardIndex = r.RewardIndex,
productRow.Rewards.Add(new BuildDeckProductRewardEntry RewardType = r.RewardType,
{ RewardDetailId = r.RewardDetailId,
RewardIndex = idx, RewardNumber = r.RewardNumber,
RewardType = GetInt(r.Value, "reward_type"), MessageId = r.MessageId,
RewardDetailId = GetLong(r.Value, "reward_detail_id"), });
RewardNumber = GetInt(r.Value, "reward_number"),
MessageId = GetInt(r.Value, "message_id"),
});
}
} }
capturedThisSeries.Add(productRow);
touchedProducts++; touchedProducts++;
} }
// Second pass: backfill missing tier per-series when sibling products share a unique value.
BackfillSeriesTier(capturedThisSeries);
} }
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -275,63 +223,6 @@ public class BuildDeckImporter
return touchedSeries + touchedProducts; return touchedSeries + touchedProducts;
} }
private static void BackfillSeriesTier(IReadOnlyList<BuildDeckProductEntry> productsInSeries)
{
// For each (Currency, Tier) pair, if all populated values across siblings are the same,
// propagate that value to products that are missing the corresponding tier.
BackfillIntroCrystal(productsInSeries);
BackfillRegularCrystal(productsInSeries);
BackfillIntroRupy(productsInSeries);
BackfillRegularRupy(productsInSeries);
}
private static void BackfillIntroCrystal(IReadOnlyList<BuildDeckProductEntry> products)
{
var distinct = products.Where(p => p.IntroPriceCrystal.HasValue).Select(p => p.IntroPriceCrystal!.Value).Distinct().ToList();
if (distinct.Count != 1) return;
int value = distinct[0];
foreach (var p in products)
{
if (p.IntroPriceCrystal is null) p.IntroPriceCrystal = value;
}
}
private static void BackfillRegularCrystal(IReadOnlyList<BuildDeckProductEntry> products)
{
var distinct = products.Where(p => p.RegularPriceCrystal.HasValue).Select(p => p.RegularPriceCrystal!.Value).Distinct().ToList();
if (distinct.Count != 1) return;
int value = distinct[0];
foreach (var p in products)
{
// For PurchaseNumMax == 1 products, never backfill the Regular tier — they have no second buy.
if (p.PurchaseNumMax <= 1) continue;
if (p.RegularPriceCrystal is null) p.RegularPriceCrystal = value;
}
}
private static void BackfillIntroRupy(IReadOnlyList<BuildDeckProductEntry> products)
{
var distinct = products.Where(p => p.IntroPriceRupy.HasValue).Select(p => p.IntroPriceRupy!.Value).Distinct().ToList();
if (distinct.Count != 1) return;
int value = distinct[0];
foreach (var p in products)
{
if (p.IntroPriceRupy is null) p.IntroPriceRupy = value;
}
}
private static void BackfillRegularRupy(IReadOnlyList<BuildDeckProductEntry> products)
{
var distinct = products.Where(p => p.RegularPriceRupy.HasValue).Select(p => p.RegularPriceRupy!.Value).Distinct().ToList();
if (distinct.Count != 1) return;
int value = distinct[0];
foreach (var p in products)
{
if (p.PurchaseNumMax <= 1) continue;
if (p.RegularPriceRupy is null) p.RegularPriceRupy = value;
}
}
/// <summary> /// <summary>
/// Maps a product_id to its series_id using the numeric pattern derived from the /info capture /// Maps a product_id to its series_id using the numeric pattern derived from the /info capture
/// and CSV inspection. /// and CSV inspection.

View File

@@ -0,0 +1,165 @@
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 the six card-id-keyed tables from load-index seeds:
/// SpotCards, ReprintedCards, UnlimitedRestrictions, LoadingExclusionCards,
/// MaintenanceCards, FeatureMaintenances. Loads the Cards FK set once for orphan warnings.
/// Rows missing from a seed are LEFT INTACT — a partial seed shouldn't silently delete entries.
/// FeatureMaintenances clears-and-rewrites because its synthetic ordinal Id has no natural-key semantics.
/// </summary>
public class CardListsImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
var knownCards = new HashSet<long>(await context.Cards.Select(c => c.Id).ToListAsync());
int total = 0;
total += await ImportSpot(context, seedDir, knownCards);
total += await ImportReprinted(context, seedDir, knownCards);
total += await ImportUnlimited(context, seedDir, knownCards);
total += await ImportLoadingExclusion(context, seedDir, knownCards);
total += await ImportMaintenance(context, seedDir);
total += await ImportFeatureMaintenances(context, seedDir);
await context.SaveChangesAsync();
return total;
}
private async Task<int> ImportSpot(SVSimDbContext context, string seedDir, HashSet<long> knownCards)
{
var seed = SeedLoader.LoadList<SpotCardSeed>(Path.Combine(seedDir, "spot-cards.json"));
if (seed.Count == 0) return 0;
var existing = await context.SpotCards.ToDictionaryAsync(e => e.Id);
int created = 0, updated = 0, orphans = 0;
foreach (var s in seed)
{
if (s.CardId == 0) continue;
if (!knownCards.Contains(s.CardId)) orphans++;
var entry = existing.TryGetValue(s.CardId, out var ex) ? ex : new SpotCardEntry { Id = s.CardId };
entry.Cost = s.Cost;
if (ex is null) { context.SpotCards.Add(entry); existing[s.CardId] = entry; created++; }
else updated++;
}
WarnOrphans("SpotCards", orphans);
Console.WriteLine($"[CardListsImporter] SpotCards +{created}/~{updated}");
return created + updated;
}
private async Task<int> ImportReprinted(SVSimDbContext context, string seedDir, HashSet<long> knownCards)
{
var seed = SeedLoader.LoadList<ReprintedCardSeed>(Path.Combine(seedDir, "reprinted-cards.json"));
if (seed.Count == 0) return 0;
var existing = await context.ReprintedCards.ToDictionaryAsync(e => e.Id);
int created = 0, orphans = 0;
foreach (var s in seed)
{
if (s.CardId == 0) continue;
if (!knownCards.Contains(s.CardId)) orphans++;
if (existing.ContainsKey(s.CardId)) continue;
var entry = new ReprintedCardEntry { Id = s.CardId };
context.ReprintedCards.Add(entry);
existing[s.CardId] = entry;
created++;
}
WarnOrphans("ReprintedCards", orphans);
Console.WriteLine($"[CardListsImporter] ReprintedCards +{created}");
return created;
}
private async Task<int> ImportUnlimited(SVSimDbContext context, string seedDir, HashSet<long> knownCards)
{
var seed = SeedLoader.LoadList<UnlimitedRestrictionSeed>(Path.Combine(seedDir, "unlimited-restrictions.json"));
if (seed.Count == 0) return 0;
var existing = await context.UnlimitedRestrictions.ToDictionaryAsync(e => e.Id);
int created = 0, updated = 0, orphans = 0;
foreach (var s in seed)
{
if (s.CardId == 0) continue;
if (!knownCards.Contains(s.CardId)) orphans++;
var entry = existing.TryGetValue(s.CardId, out var ex) ? ex : new UnlimitedRestrictionEntry { Id = s.CardId };
entry.RestrictionValue = s.RestrictionValue;
if (ex is null) { context.UnlimitedRestrictions.Add(entry); existing[s.CardId] = entry; created++; }
else updated++;
}
WarnOrphans("UnlimitedRestrictions", orphans);
Console.WriteLine($"[CardListsImporter] UnlimitedRestrictions +{created}/~{updated}");
return created + updated;
}
private async Task<int> ImportLoadingExclusion(SVSimDbContext context, string seedDir, HashSet<long> knownCards)
{
var seed = SeedLoader.LoadList<LoadingExclusionCardSeed>(Path.Combine(seedDir, "loading-exclusion-cards.json"));
if (seed.Count == 0) return 0;
var existing = await context.LoadingExclusionCards.ToDictionaryAsync(e => e.Id);
int created = 0, orphans = 0;
foreach (var s in seed)
{
if (s.CardId == 0) continue;
if (!knownCards.Contains(s.CardId)) orphans++;
if (existing.ContainsKey(s.CardId)) continue;
var entry = new LoadingExclusionCardEntry { Id = s.CardId };
context.LoadingExclusionCards.Add(entry);
existing[s.CardId] = entry;
created++;
}
WarnOrphans("LoadingExclusionCards", orphans);
Console.WriteLine($"[CardListsImporter] LoadingExclusionCards +{created}");
return created;
}
private async Task<int> ImportMaintenance(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<MaintenanceCardSeed>(Path.Combine(seedDir, "maintenance-cards.json"));
if (seed.Count == 0) return 0;
var existing = await context.MaintenanceCards.ToDictionaryAsync(e => e.Id);
int created = 0;
foreach (var s in seed)
{
if (s.CardId == 0) continue;
if (existing.ContainsKey(s.CardId)) continue;
var entry = new MaintenanceCardEntry { Id = s.CardId };
context.MaintenanceCards.Add(entry);
existing[s.CardId] = entry;
created++;
}
Console.WriteLine($"[CardListsImporter] MaintenanceCards +{created}");
return created;
}
private async Task<int> ImportFeatureMaintenances(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<FeatureMaintenanceSeed>(Path.Combine(seedDir, "feature-maintenances.json"));
if (seed.Count == 0) return 0;
// FeatureMaintenances has a synthetic int Id assigned by the extractor (1-based ordinal).
// FeatureMaintenances use a synthetic ordinal id from the extractor; we clear-and-rewrite to
// keep re-runs idempotent and match "the latest seed is authoritative". Pre-existing rows
// with seed-absent ids are dropped here (acceptable: only synthetic ordinals, no FKs
// reference this table).
var existing = await context.FeatureMaintenances.ToListAsync();
context.FeatureMaintenances.RemoveRange(existing);
int created = 0;
foreach (var s in seed)
{
if (s.Id == 0) continue;
context.FeatureMaintenances.Add(new FeatureMaintenanceEntry
{
Id = s.Id,
FeatureKey = s.FeatureKey,
Data = s.Data.ValueKind == JsonValueKind.Undefined ? "{}" : JsonSerializer.Serialize(s.Data),
});
created++;
}
Console.WriteLine($"[CardListsImporter] FeatureMaintenances: -{existing.Count}/+{created}");
return created;
}
private static void WarnOrphans(string label, int count)
{
if (count > 0)
Console.Error.WriteLine($"[CardListsImporter] Warning: {label} has {count} orphan card_id(s) — run CardImporter first for clean references.");
}
}

View 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;
}
}

View File

@@ -0,0 +1,60 @@
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 default decks from <c>seeds/default-decks.json</c>. Warns on orphan card
/// references (card_id not in Cards table) but never fails — CardImporter must run first for a
/// clean warning-free run. Rows missing from the seed are LEFT INTACT.
/// </summary>
public class DefaultDeckImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<DefaultDeckSeed>(Path.Combine(seedDir, "default-decks.json"));
if (seed.Count == 0)
{
Console.WriteLine("[DefaultDeckImporter] No seed rows; skipping.");
return 0;
}
var existing = await context.DefaultDecks.ToDictionaryAsync(e => e.Id);
var knownCards = new HashSet<long>(await context.Cards.Select(c => c.Id).ToListAsync());
int created = 0, updated = 0, orphans = 0;
foreach (var s in seed)
{
if (s.Id == 0) continue;
var entry = existing.TryGetValue(s.Id, out var ex) ? ex : new DefaultDeckEntry { Id = s.Id };
entry.ClassId = s.ClassId;
entry.SleeveId = s.SleeveId;
entry.LeaderSkinId = s.LeaderSkinId;
entry.DeckName = s.DeckName;
entry.CardIdArray = JsonSerializer.Serialize(s.CardIdArray);
// Orphan count against card master — informational, never throws.
foreach (var cardId in s.CardIdArray)
{
if (!knownCards.Contains(cardId)) orphans++;
}
if (ex is null) { context.DefaultDecks.Add(entry); existing[s.Id] = entry; created++; }
else updated++;
}
await context.SaveChangesAsync();
WarnOrphans("DefaultDecks.card_id_array", orphans);
Console.WriteLine($"[DefaultDeckImporter] +{created}/~{updated}");
return created + updated;
}
private static void WarnOrphans(string label, int count)
{
if (count > 0)
Console.Error.WriteLine($"[DefaultDeckImporter] Warning: {label} has {count} orphan card_id(s) — run CardImporter first for clean references.");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,84 +1,13 @@
using System.Text.Json;
namespace SVSim.Bootstrap.Importers; namespace SVSim.Bootstrap.Importers;
/// <summary> /// <summary>
/// Shared helpers for content importers. Loads a prod-capture JSON file by endpoint name from /// Tiny shared helper for content importers. Capture parsing has moved out of the bootstrap
/// a captures directory, returning the inner <c>data</c> element. Picks the latest matching dated /// project entirely (extractors under <c>data_dumps/extract/</c> emit per-table seed JSON);
/// file (e.g. <c>load-index-2026-05-23.json</c>) if multiple exist for the same endpoint. /// only the wire-date normaliser stays here because several seed-driven importers still need
/// to canonicalise prod-shaped timestamp strings.
/// </summary> /// </summary>
public static class ImporterBase public static class ImporterBase
{ {
/// <summary>
/// Returns the parsed <c>.data</c> JsonElement for the latest <c>{endpoint}-*.json</c> file in
/// <paramref name="capturesDir"/>, or null if no file matches. Logs a warning when missing —
/// caller decides whether absence is fatal.
/// </summary>
public static JsonElement? LoadCapture(string capturesDir, string endpoint)
{
if (!Directory.Exists(capturesDir))
{
Console.Error.WriteLine($"[ImporterBase] Captures dir missing: {capturesDir}");
return null;
}
string? path = Directory.EnumerateFiles(capturesDir, $"{endpoint}-*.json")
.OrderByDescending(p => p)
.FirstOrDefault();
if (path is null)
{
Console.Error.WriteLine($"[ImporterBase] No capture found for endpoint '{endpoint}' in {capturesDir}");
return null;
}
using var fs = File.OpenRead(path);
using var doc = JsonDocument.Parse(fs);
if (!doc.RootElement.TryGetProperty("data", out var data))
{
Console.Error.WriteLine($"[ImporterBase] Capture file {path} has no top-level 'data' property.");
return null;
}
// Clone so the JsonElement survives doc disposal.
return data.Clone();
}
/// <summary>
/// Generic upsert by primary key. Returns (created, updated, unchanged) counts.
/// <paramref name="incoming"/> is the desired state from the capture; rows are matched by
/// <paramref name="keySelector"/>. <paramref name="applyChanges"/> mutates an existing row to
/// reflect incoming values and returns true if anything actually changed.
/// </summary>
public static (int created, int updated, int unchanged) Upsert<T, TKey>(
IEnumerable<T> incoming,
Dictionary<TKey, T> existingByKey,
Func<T, TKey> keySelector,
Action<T> addToContext,
Func<T, T, bool> applyChanges) where TKey : notnull
{
int created = 0, updated = 0, unchanged = 0;
foreach (var item in incoming)
{
var key = keySelector(item);
if (existingByKey.TryGetValue(key, out var existing))
{
if (applyChanges(existing, item)) updated++;
else unchanged++;
}
else
{
addToContext(item);
existingByKey[key] = item;
created++;
}
}
return (created, updated, unchanged);
}
/// <summary>Serialize a JsonElement back to compact JSON text for jsonb storage.</summary>
public static string Serialize(JsonElement el) =>
JsonSerializer.Serialize(el, new JsonSerializerOptions { WriteIndented = false });
/// <summary>Parse a wire date that may be ISO ("2026-05-23T..."), space-separated ("2026-05-23 16:32:31"), or empty.</summary> /// <summary>Parse a wire date that may be ISO ("2026-05-23T..."), space-separated ("2026-05-23 16:32:31"), or empty.</summary>
public static DateTime ParseWireDateTime(string? s) public static DateTime ParseWireDateTime(string? s)
{ {
@@ -91,50 +20,4 @@ public static class ImporterBase
} }
return DateTime.MinValue; return DateTime.MinValue;
} }
/// <summary>Read a JsonElement string/number property as long, defaulting on missing/null.</summary>
public static long GetLong(JsonElement el, string prop, long fallback = 0)
{
if (!el.TryGetProperty(prop, out var v) || v.ValueKind == JsonValueKind.Null) return fallback;
if (v.ValueKind == JsonValueKind.Number) return v.GetInt64();
if (v.ValueKind == JsonValueKind.String && long.TryParse(v.GetString(), out var n)) return n;
return fallback;
}
public static int GetInt(JsonElement el, string prop, int fallback = 0)
{
if (!el.TryGetProperty(prop, out var v) || v.ValueKind == JsonValueKind.Null) return fallback;
if (v.ValueKind == JsonValueKind.Number) return v.GetInt32();
if (v.ValueKind == JsonValueKind.String && int.TryParse(v.GetString(), out var n)) return n;
return fallback;
}
public static string GetString(JsonElement el, string prop, string fallback = "")
{
if (!el.TryGetProperty(prop, out var v) || v.ValueKind == JsonValueKind.Null) return fallback;
return v.ValueKind == JsonValueKind.String ? v.GetString() ?? fallback : v.ToString();
}
public static bool GetBool(JsonElement el, string prop, bool fallback = false)
{
if (!el.TryGetProperty(prop, out var v) || v.ValueKind == JsonValueKind.Null) return fallback;
if (v.ValueKind == JsonValueKind.True) return true;
if (v.ValueKind == JsonValueKind.False) return false;
if (v.ValueKind == JsonValueKind.Number) return v.GetInt32() != 0;
if (v.ValueKind == JsonValueKind.String)
{
var s = v.GetString();
if (bool.TryParse(s, out var b)) return b;
if (int.TryParse(s, out var i)) return i != 0;
}
return fallback;
}
public static ulong GetULong(JsonElement el, string prop, ulong fallback = 0)
{
if (!el.TryGetProperty(prop, out var v) || v.ValueKind == JsonValueKind.Null) return fallback;
if (v.ValueKind == JsonValueKind.Number) return v.GetUInt64();
if (v.ValueKind == JsonValueKind.String && ulong.TryParse(v.GetString(), out var n)) return n;
return fallback;
}
} }

View File

@@ -0,0 +1,185 @@
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 /mypage/index-derived globals from per-table seed files.
/// Banners and SpecialDeckFormats use CLEAR-AND-REWRITE semantics (no stable wire ID, capture is authoritative).
/// Colosseum and SealedSeason are singletons (Id=1). MasterPointRankingPeriod upserts by wire id.
/// </summary>
public class MyPageGlobalsImporter
{
public async Task<int> ImportBannersAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<BannerSeed>(Path.Combine(seedDir, "banners.json"));
if (seed.Count == 0)
{
Console.WriteLine("[MyPageGlobalsImporter] No banner seed rows; skipping.");
return 0;
}
// Clear-and-rewrite: banners have no stable wire ID, the capture is authoritative.
var existing = await context.Banners.ToListAsync();
context.Banners.RemoveRange(existing);
foreach (var s in seed)
{
context.Banners.Add(new BannerEntry
{
Id = s.Id,
ImageName = s.ImageName,
Click = s.Click,
Status = s.Status,
ChangeTime = s.ChangeTime,
RemainingTime = s.RemainingTime,
ImagePaths = s.ImagePaths.ValueKind == JsonValueKind.Undefined
? "[]"
: JsonSerializer.Serialize(s.ImagePaths),
});
}
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] Banners: -{existing.Count}/+{seed.Count}");
return seed.Count;
}
public async Task<int> ImportColosseumAsync(SVSimDbContext context, string seedDir)
{
var s = SeedLoader.LoadObject<ColosseumSeed>(Path.Combine(seedDir, "colosseum.json"));
if (s is null)
{
Console.WriteLine("[MyPageGlobalsImporter] No colosseum seed; skipping.");
return 0;
}
var existing = await context.Colosseums.FirstOrDefaultAsync(e => e.Id == 1);
var entry = existing ?? new ColosseumConfig { Id = 1 };
entry.ColosseumId = s.ColosseumId;
entry.ColosseumName = s.ColosseumName;
entry.CardPoolName = s.CardPoolName;
entry.DeckFormat = s.DeckFormat;
entry.StartTime = ImporterBase.ParseWireDateTime(s.StartTime);
entry.EndTime = ImporterBase.ParseWireDateTime(s.EndTime);
entry.NowRound = s.NowRound;
entry.IsDisplayTips = s.IsDisplayTips;
entry.TipsId = s.TipsId;
entry.IsColosseumPeriod = s.IsColosseumPeriod;
entry.IsRoundPeriod = s.IsRoundPeriod;
entry.IsNormalTwoPick = s.IsNormalTwoPick;
entry.IsSpecialMode = s.IsSpecialMode;
entry.IsAllCardEnabled = s.IsAllCardEnabled;
entry.SalesPeriodInfo = s.SalesPeriodInfo.ValueKind == JsonValueKind.Undefined
? "{}"
: JsonSerializer.Serialize(s.SalesPeriodInfo);
if (existing is null) context.Colosseums.Add(entry);
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] Colosseum: {(existing is null ? "+1" : "~1")}");
return 1;
}
public async Task<int> ImportSealedAsync(SVSimDbContext context, string seedDir)
{
var s = SeedLoader.LoadObject<SealedSeasonSeed>(Path.Combine(seedDir, "sealed-season.json"));
if (s is null)
{
Console.WriteLine("[MyPageGlobalsImporter] No sealed-season seed; skipping.");
return 0;
}
var existing = await context.SealedSeasons.FirstOrDefaultAsync(e => e.Id == 1);
var entry = existing ?? new SealedConfig { Id = 1 };
entry.Enable = s.Enable;
entry.CrystalCost = s.CrystalCost;
entry.RupyCost = s.RupyCost;
entry.TicketCost = s.TicketCost;
entry.DeckUsingNumMin = s.DeckUsingNumMin;
entry.ScheduleId = s.ScheduleId;
entry.IsJoin = s.IsJoin;
entry.IsDeckCodeMaintenance = s.IsDeckCodeMaintenance;
entry.PackInfo = s.PackInfo.ValueKind == JsonValueKind.Undefined
? "[]"
: JsonSerializer.Serialize(s.PackInfo);
entry.SalesPeriodInfo = s.SalesPeriodInfo.ValueKind == JsonValueKind.Undefined
? "{}"
: JsonSerializer.Serialize(s.SalesPeriodInfo);
if (existing is null) context.SealedSeasons.Add(entry);
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] SealedSeason: {(existing is null ? "+1" : "~1")}");
return 1;
}
public async Task<int> ImportMasterPointRankingPeriodAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<MasterPointRankingPeriodSeed>(
Path.Combine(seedDir, "master-point-ranking-periods.json"));
if (seed.Count == 0)
{
Console.WriteLine("[MyPageGlobalsImporter] No master-point-ranking-period seed rows; skipping.");
return 0;
}
var existing = await context.MasterPointRankingPeriods.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 MasterPointRankingPeriodEntry { Id = s.Id };
entry.PeriodNum = s.PeriodNum;
entry.NecessaryScore = s.NecessaryScore;
entry.BeginTime = ImporterBase.ParseWireDateTime(s.BeginTime);
entry.EndTime = ImporterBase.ParseWireDateTime(s.EndTime);
if (ex is null)
{
context.MasterPointRankingPeriods.Add(entry);
existing[s.Id] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] MasterPointRankingPeriod: +{created}/~{updated}");
return created + updated;
}
public async Task<int> ImportSpecialDeckFormatsAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<SpecialDeckFormatSeed>(
Path.Combine(seedDir, "special-deck-formats.json"));
if (seed.Count == 0)
{
Console.WriteLine("[MyPageGlobalsImporter] No special-deck-format seed rows; skipping.");
return 0;
}
// Clear-and-rewrite: same semantics as banners — no stable wire ID, capture is authoritative.
var existing = await context.SpecialDeckFormats.ToListAsync();
context.SpecialDeckFormats.RemoveRange(existing);
foreach (var s in seed)
{
context.SpecialDeckFormats.Add(new SpecialDeckFormatEntry
{
Id = s.Id,
DeckFormat = s.DeckFormat,
EndTime = ImporterBase.ParseWireDateTime(s.EndTime),
});
}
await context.SaveChangesAsync();
Console.WriteLine($"[MyPageGlobalsImporter] SpecialDeckFormats: -{existing.Count}/+{seed.Count}");
return seed.Count;
}
}

View 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;
}
}

View File

@@ -0,0 +1,107 @@
using Microsoft.EntityFrameworkCore;
using SVSim.Bootstrap.Models.Seed;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using static SVSim.Bootstrap.Importers.ImporterBase;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Idempotent upsert of /pack/info catalog from <c>seeds/packs.json</c>. Owned collections
/// (ChildGachas, Banners) are replaced wholesale per pack (clear-then-rehydrate) -- diffing owned
/// collections by composite keys is more code than it's worth for catalog updates, and this
/// matches the wholesale-replace semantics of the previous in-line ImportPacks implementation.
/// Rows missing from the seed are LEFT INTACT.
/// </summary>
public class PackImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
var seed = SeedLoader.LoadList<PackSeed>(Path.Combine(seedDir, "packs.json"));
if (seed.Count == 0)
{
Console.WriteLine("[PackImporter] No seed rows; skipping.");
return 0;
}
var existing = await context.Packs
.Include(p => p.ChildGachas)
.Include(p => p.Banners)
.ToDictionaryAsync(p => p.Id);
int created = 0, updated = 0;
foreach (var s in seed)
{
if (s.ParentGachaId == 0) continue;
var pack = existing.TryGetValue(s.ParentGachaId, out var ex)
? ex : new PackConfigEntry { Id = s.ParentGachaId };
pack.BasePackId = s.BasePackId;
pack.GachaType = s.GachaType;
pack.PackCategory = (PackCategory)s.PackCategory;
pack.PosterType = s.PosterType;
pack.CommenceDate = ParseWireDateTime(s.CommenceDate);
pack.CompleteDate = ParseWireDateTime(s.CompleteDate);
pack.SleeveId = s.SleeveId;
pack.SpecialSleeveId = s.SpecialSleeveId;
pack.OverrideDrawEffectPackId = s.OverrideDrawEffectPackId;
pack.OverrideUiEffectPackId = s.OverrideUiEffectPackId;
pack.GachaDetail = s.GachaDetail;
pack.IsHide = s.IsHide;
pack.IsNew = s.IsNew;
pack.IsPreRelease = s.IsPreRelease;
pack.OpenCountLimit = s.OpenCountLimit;
pack.SalesPeriodTime = string.IsNullOrEmpty(s.SalesPeriodTime)
? null
: ParseWireDateTime(s.SalesPeriodTime);
pack.GachaPointConfig = s.GachaPoint is null ? null : new PackGachaPointConfig
{
ExchangeablePoint = s.GachaPoint.ExchangeablePoint,
IncreaseGachaPoint = s.GachaPoint.IncreaseGachaPoint,
};
// Owned collections -- clear and rehydrate (matches the previous wholesale-replace semantics).
pack.ChildGachas.Clear();
foreach (var c in s.ChildGachas)
{
pack.ChildGachas.Add(new PackChildGachaEntry
{
GachaId = c.GachaId,
TypeDetail = c.TypeDetail,
Cost = c.Cost,
CardCount = c.CardCount,
ItemId = c.ItemId,
IsDailySingle = c.IsDailySingle,
OverrideIncreaseGachaPoint = c.OverrideIncreaseGachaPoint,
PurchaseLimitCount = c.PurchaseLimitCount,
FreeGachaCampaignId = c.FreeGachaCampaignId,
CampaignName = c.CampaignName,
});
}
pack.Banners.Clear();
foreach (var b in s.Banners)
{
pack.Banners.Add(new PackBannerEntry
{
BannerName = b.BannerName,
DialogTitle = b.DialogTitle,
});
}
if (ex is null)
{
context.Packs.Add(pack);
existing[s.ParentGachaId] = pack;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[PackImporter] +{created}/~{updated}");
return created + updated;
}
}

View File

@@ -0,0 +1,64 @@
using System.Globalization;
using Microsoft.EntityFrameworkCore;
using SVSim.Bootstrap.Models.Seed;
using SVSim.Database;
using SVSim.Database.Models;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Idempotent upsert of Steam payment items from <c>seeds/payment-items.json</c>.
/// Rows missing from the seed are LEFT INTACT.
/// </summary>
public class PaymentItemImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
string path = Path.Combine(seedDir, "payment-items.json");
var seed = SeedLoader.LoadList<PaymentItemSeed>(path);
if (seed.Count == 0)
{
Console.WriteLine("[PaymentItemImporter] No seed rows; skipping.");
return 0;
}
var existing = await context.PaymentItems.ToDictionaryAsync(e => e.Id);
int created = 0, updated = 0;
foreach (var s in seed)
{
if (s.RecordId == 0) continue;
var entry = existing.TryGetValue(s.RecordId, out var ex)
? ex : new PaymentItemEntry { Id = s.RecordId };
entry.ProductId = s.ProductId;
entry.StoreProductId = s.StoreProductId;
entry.Name = s.Name;
entry.Text = s.Text;
entry.Price = decimal.TryParse(s.Price, NumberStyles.Number, CultureInfo.InvariantCulture, out var d) ? d : 0m;
entry.ChargeCrystalNum = s.ChargeCrystalNum;
entry.FreeCrystalNum = s.FreeCrystalNum;
entry.PurchaseLimit = s.PurchaseLimit;
entry.SpecialShopFlag = s.SpecialShopFlag;
entry.ImageName = s.ImageName;
entry.StartTime = ImporterBase.ParseWireDateTime(s.StartTime);
entry.EndTime = ImporterBase.ParseWireDateTime(s.EndTime);
entry.RemainingTime = s.RemainingTime;
entry.IsResaleProduct = s.IsResaleProduct;
entry.ResaleStartDate = string.IsNullOrWhiteSpace(s.ResaleStartDate) ? null : ImporterBase.ParseWireDateTime(s.ResaleStartDate);
if (ex is null)
{
context.PaymentItems.Add(entry);
existing[s.RecordId] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[PaymentItemImporter] +{created}/~{updated}");
return created + updated;
}
}

View File

@@ -0,0 +1,59 @@
using Microsoft.EntityFrameworkCore;
using SVSim.Bootstrap.Models.Seed;
using SVSim.Database;
using SVSim.Database.Models;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Idempotent upsert of practice opponents from <c>seeds/practice-opponents.json</c>.
/// Rows missing from the seed are LEFT INTACT (consistent with the previous import behavior;
/// a partial seed shouldn't silently delete entries).
/// </summary>
public class PracticeOpponentImporter
{
public async Task<int> ImportAsync(SVSimDbContext context, string seedDir)
{
string path = Path.Combine(seedDir, "practice-opponents.json");
var seed = SeedLoader.LoadList<PracticeOpponentSeed>(path);
if (seed.Count == 0)
{
Console.WriteLine("[PracticeOpponentImporter] No seed rows; skipping.");
return 0;
}
var existing = await context.PracticeOpponents.ToDictionaryAsync(e => e.Id);
int created = 0, updated = 0;
foreach (var s in seed)
{
if (s.PracticeId == 0) continue;
var entry = existing.TryGetValue(s.PracticeId, out var ex)
? ex : new PracticeOpponentEntry { Id = s.PracticeId };
entry.TextId = s.TextId;
entry.ClassId = s.ClassId;
entry.CharaId = s.CharaId;
entry.DegreeId = s.DegreeId;
entry.AiDeckLevel = s.AiDeckLevel;
entry.AiLogicLevel = s.AiLogicLevel;
entry.AiMaxLife = s.AiMaxLife;
entry.Battle3dFieldId = s.Battle3dFieldId;
entry.IsMaintenance = s.IsMaintenance;
entry.IsCampaignPractice = s.IsCampaignPractice;
if (ex is null)
{
context.PracticeOpponents.Add(entry);
existing[s.PracticeId] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[PracticeOpponentImporter] +{created}/~{updated}");
return created + updated;
}
}

View 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;
}
}

View File

@@ -0,0 +1,142 @@
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 the basic-puzzle catalog from <c>seeds/puzzle-groups.json</c>,
/// <c>seeds/puzzles.json</c>, and <c>seeds/puzzle-missions.json</c>. Groups must be imported
/// before puzzles (FK on <see cref="PuzzleEntry.GroupId"/> -> <see cref="PuzzleGroupEntry.Id"/>).
/// Rows missing from the seed are LEFT INTACT (consistent with other per-importer seeds).
/// </summary>
public class PuzzleImporter
{
public async Task<int> ImportGroupsAsync(SVSimDbContext context, string seedDir)
{
string path = Path.Combine(seedDir, "puzzle-groups.json");
var seed = SeedLoader.LoadList<PuzzleGroupSeed>(path);
if (seed.Count == 0)
{
Console.WriteLine("[PuzzleImporter] No group seed rows; skipping.");
return 0;
}
var existing = await context.PuzzleGroups.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 PuzzleGroupEntry { Id = s.Id };
entry.BasicTitleTextId = s.BasicTitleTextId;
entry.PuzzleCharaId = s.PuzzleCharaId;
entry.CharaId = s.CharaId;
entry.SortType = s.SortType;
entry.DifficultyNameListJson = s.DifficultyNameList.ValueKind == JsonValueKind.Undefined
? "{}"
: JsonSerializer.Serialize(s.DifficultyNameList);
if (ex is null)
{
context.PuzzleGroups.Add(entry);
existing[s.Id] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[PuzzleImporter] Groups +{created}/~{updated}");
return created + updated;
}
public async Task<int> ImportPuzzlesAsync(SVSimDbContext context, string seedDir)
{
string path = Path.Combine(seedDir, "puzzles.json");
var seed = SeedLoader.LoadList<PuzzleSeed>(path);
if (seed.Count == 0)
{
Console.WriteLine("[PuzzleImporter] No puzzle seed rows; skipping.");
return 0;
}
var existing = await context.Puzzles.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 PuzzleEntry { Id = s.Id };
entry.GroupId = s.GroupId;
entry.PuzzleDifficulty = s.PuzzleDifficulty;
entry.IsAdditional = s.IsAdditional;
entry.IsPlayable = s.IsPlayable;
entry.ReleaseConditionTextId = s.ReleaseConditionTextId;
if (ex is null)
{
context.Puzzles.Add(entry);
existing[s.Id] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[PuzzleImporter] Puzzles +{created}/~{updated}");
return created + updated;
}
public async Task<int> ImportMissionsAsync(SVSimDbContext context, string seedDir)
{
string path = Path.Combine(seedDir, "puzzle-missions.json");
var seed = SeedLoader.LoadList<PuzzleMissionSeed>(path);
if (seed.Count == 0)
{
Console.WriteLine("[PuzzleImporter] No mission seed rows; skipping.");
return 0;
}
var existing = await context.PuzzleMissions.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 PuzzleMissionEntry { Id = s.Id };
entry.MissionName = s.MissionName;
entry.AchievedMessage = s.AchievedMessage;
entry.RequireNumber = s.RequireNumber;
entry.CampaignCommenceTime = s.CampaignCommenceTime;
entry.OrderId = s.OrderId;
entry.RewardType = s.RewardType;
entry.RewardDetailId = s.RewardDetailId;
entry.RewardNumber = s.RewardNumber;
entry.TargetPuzzleGroupId = s.TargetPuzzleGroupId;
if (ex is null)
{
context.PuzzleMissions.Add(entry);
existing[s.Id] = entry;
created++;
}
else updated++;
}
await context.SaveChangesAsync();
Console.WriteLine($"[PuzzleImporter] Missions +{created}/~{updated}");
return created + updated;
}
}

View File

@@ -0,0 +1,117 @@
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;
c.RotationCardSetIds = rot.RotationCardSetIds ?? new List<int>();
});
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)
{
// Schedule windows are intentionally parsed WITHOUT AssumeUniversal because the seed
// strings ("2024-05-01 20:00:00") are timezone-less and the rest of the pipeline (the
// [ConfigSection] JSON round-trip + LoadController's wire mapping) treats them as
// local-kind ticks. Mirrors the legacy GlobalsImporter.TryParseScheduleWindow behavior
// — see GlobalsRepositoryTests for the round-trip assertion.
var gBegin = ParseScheduleWireDateTime(schedule.Gathering.Begin);
var gEnd = ParseScheduleWireDateTime(schedule.Gathering.End);
var fBegin = ParseScheduleWireDateTime(schedule.FreeBattle.Begin);
var fEnd = ParseScheduleWireDateTime(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;
}
// Legacy schedule-window parse: default styles (AssumeLocal), matching the original
// GlobalsImporter.TryParseScheduleWindow. The schedule strings are timezone-less; preserving
// legacy local-kind ticks keeps the wire output byte-equivalent across the migration.
private static DateTime ParseScheduleWireDateTime(string? s)
{
if (string.IsNullOrWhiteSpace(s)) return DateTime.MinValue;
return DateTime.TryParse(s, out var dt) ? dt : DateTime.MinValue;
}
// 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);
}
}

View File

@@ -0,0 +1,64 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.Database.Models.Config;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Reads <see cref="RotationConfig"/> from the GameConfigs table (populated by
/// <see cref="RotationConfigImporter"/>) and flips <c>CardSet.IsInRotation</c> to match.
/// Must run after RotationConfigImporter and CardImporter — CardSets missing from the DB
/// can't be promoted (we log a warning instead of failing — the rotation flag flip is non-fatal).
/// </summary>
public class RotationFlagUpdater
{
public async Task<int> UpdateAsync(SVSimDbContext context)
{
var sectionName = typeof(RotationConfig).GetCustomAttributes(typeof(ConfigSectionAttribute), inherit: false)
.Cast<ConfigSectionAttribute>().FirstOrDefault()?.Name
?? throw new InvalidOperationException("RotationConfig missing [ConfigSection]");
var row = await context.GameConfigs.FirstOrDefaultAsync(s => s.SectionName == sectionName);
if (row is null)
{
Console.WriteLine("[RotationFlagUpdater] No Rotation section in GameConfigs; skipping.");
return 0;
}
var cfg = JsonSerializer.Deserialize<RotationConfig>(row.ValueJson);
if (cfg is null)
{
Console.WriteLine("[RotationFlagUpdater] Failed to deserialize RotationConfig; skipping.");
return 0;
}
var rotationSet = (cfg.RotationCardSetIds ?? new List<int>()).ToHashSet();
if (rotationSet.Count == 0)
{
Console.WriteLine("[RotationFlagUpdater] RotationCardSetIds empty; no flag changes.");
return 0;
}
var allSets = await context.CardSets.ToListAsync();
int updated = 0, missing = 0;
foreach (var rid in rotationSet)
{
var set = allSets.FirstOrDefault(s => s.Id == rid);
if (set is null) { missing++; continue; }
if (!set.IsInRotation) { set.IsInRotation = true; updated++; }
}
// Demote sets not in the current rotation.
foreach (var s in allSets.Where(s => s.IsInRotation && !rotationSet.Contains(s.Id)))
{
s.IsInRotation = false;
updated++;
}
if (missing > 0)
Console.Error.WriteLine($"[RotationFlagUpdater] Warning: {missing} rotation card_set_id(s) missing from CardSets — run CardImporter first.");
await context.SaveChangesAsync();
Console.WriteLine($"[RotationFlagUpdater] CardSet.IsInRotation ~{updated}");
return updated;
}
}

View File

@@ -0,0 +1,42 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Importers;
/// <summary>
/// Reads a JSON seed file under <c>SVSim.Bootstrap/Data/seeds/</c>. Replaces ImporterBase.LoadCapture.
/// Files are produced by extractors in <c>data_dumps/extract/</c>; the bootstrap project does not
/// transform wire formats. Missing files are non-fatal (returns empty/null) — caller decides.
/// </summary>
public static class SeedLoader
{
private static readonly JsonSerializerOptions Options = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
public static List<T> LoadList<T>(string path)
{
if (!File.Exists(path))
{
Console.Error.WriteLine($"[SeedLoader] Missing seed file: {path}");
return new List<T>();
}
using var fs = File.OpenRead(path);
return JsonSerializer.Deserialize<List<T>>(fs, Options) ?? new List<T>();
}
public static T? LoadObject<T>(string path) where T : class
{
if (!File.Exists(path))
{
Console.Error.WriteLine($"[SeedLoader] Missing seed file: {path}");
return null;
}
using var fs = File.OpenRead(path);
return JsonSerializer.Deserialize<T>(fs, Options);
}
}

View File

View 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; }
}

View 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; } = "";
}

View File

@@ -0,0 +1,15 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class BannerSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("image_name")] public string ImageName { get; set; } = "";
[JsonPropertyName("click")] public string Click { get; set; } = "";
[JsonPropertyName("status")] public string Status { get; set; } = "";
[JsonPropertyName("change_time")] public int ChangeTime { get; set; }
[JsonPropertyName("remaining_time")] public int RemainingTime { get; set; }
[JsonPropertyName("image_paths")] public JsonElement ImagePaths { get; set; }
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors a single entry in <c>seeds/battle-pass-levels.json</c>.</summary>
public sealed class BattlePassLevelSeed
{
[JsonPropertyName("level")] public int Level { get; set; }
[JsonPropertyName("required_point")] public int RequiredPoint { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors a single entry in <c>seeds/battle-pass-rewards.json</c>.</summary>
public sealed class BattlePassRewardSeed
{
[JsonPropertyName("season_id")] public int SeasonId { get; set; }
[JsonPropertyName("track")] public string Track { get; set; } = ""; // "normal" / "premium"
[JsonPropertyName("level")] public int Level { get; set; }
[JsonPropertyName("reward_type")] public int RewardType { get; set; }
[JsonPropertyName("reward_detail_id")] public long RewardDetailId { get; set; }
[JsonPropertyName("reward_number")] public int RewardNumber { get; set; }
[JsonPropertyName("is_appeal_exclusion")] public bool IsAppealExclusion { get; set; }
}

View File

@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors a single entry in <c>seeds/battle-pass-seasons.json</c>.</summary>
public sealed class BattlePassSeasonSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("name")] public string Name { get; set; } = "";
[JsonPropertyName("max_level")] public int MaxLevel { get; set; }
[JsonPropertyName("start_date")] public string StartDate { get; set; } = "";
[JsonPropertyName("end_date")] public string EndDate { get; set; } = "";
[JsonPropertyName("can_purchase")] public bool CanPurchase { get; set; }
[JsonPropertyName("price_crystal")] public int PriceCrystal { get; set; }
[JsonPropertyName("description")] public string Description { get; set; } = "";
}

View File

@@ -0,0 +1,46 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class BuildDeckCatalogSeed
{
[JsonPropertyName("series_id")] public int SeriesId { get; set; }
[JsonPropertyName("order_id")] public int OrderId { get; set; }
[JsonPropertyName("is_new")] public bool IsNew { get; set; }
[JsonPropertyName("series_rewards")] public List<BuildDeckSeriesRewardSeed> SeriesRewards { get; set; } = new();
[JsonPropertyName("products")] public List<BuildDeckProductSeed> Products { get; set; } = new();
}
public sealed class BuildDeckSeriesRewardSeed
{
[JsonPropertyName("tier_index")] public int TierIndex { get; set; }
[JsonPropertyName("item_index")] public int ItemIndex { get; set; }
[JsonPropertyName("reward_type")] public int RewardType { get; set; }
[JsonPropertyName("reward_detail_id")] public long RewardDetailId { get; set; }
[JsonPropertyName("reward_number")] public int RewardNumber { get; set; }
[JsonPropertyName("message_id")] public int MessageId { get; set; }
}
public sealed class BuildDeckProductSeed
{
[JsonPropertyName("product_id")] public int ProductId { get; set; }
[JsonPropertyName("leader_id")] public int LeaderId { get; set; }
[JsonPropertyName("deck_code")] public string DeckCode { get; set; } = "";
[JsonPropertyName("product_name")] public string ProductName { get; set; } = "";
[JsonPropertyName("featured_card_id")] public long FeaturedCardId { get; set; }
[JsonPropertyName("purchase_num_max")] public int PurchaseNumMax { get; set; }
[JsonPropertyName("intro_price_crystal")] public int? IntroPriceCrystal { get; set; }
[JsonPropertyName("regular_price_crystal")] public int? RegularPriceCrystal { get; set; }
[JsonPropertyName("intro_price_rupy")] public int? IntroPriceRupy { get; set; }
[JsonPropertyName("regular_price_rupy")] public int? RegularPriceRupy { get; set; }
[JsonPropertyName("rewards")] public List<BuildDeckProductRewardSeed> Rewards { get; set; } = new();
}
public sealed class BuildDeckProductRewardSeed
{
[JsonPropertyName("reward_index")] public int RewardIndex { get; set; }
[JsonPropertyName("reward_type")] public int RewardType { get; set; }
[JsonPropertyName("reward_detail_id")] public long RewardDetailId { get; set; }
[JsonPropertyName("reward_number")] public int RewardNumber { get; set; }
[JsonPropertyName("message_id")] public int MessageId { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class ColosseumSeed
{
[JsonPropertyName("id")] public int Id { get; set; } = 1;
[JsonPropertyName("colosseum_id")] public string ColosseumId { get; set; } = "";
[JsonPropertyName("colosseum_name")] public string ColosseumName { get; set; } = "";
[JsonPropertyName("card_pool_name")] public string CardPoolName { get; set; } = "";
[JsonPropertyName("deck_format")] public string DeckFormat { get; set; } = "";
[JsonPropertyName("start_time")] public string StartTime { get; set; } = "";
[JsonPropertyName("end_time")] public string EndTime { get; set; } = "";
[JsonPropertyName("now_round")] public string NowRound { get; set; } = "";
[JsonPropertyName("is_display_tips")] public string IsDisplayTips { get; set; } = "";
[JsonPropertyName("tips_id")] public string TipsId { get; set; } = "";
[JsonPropertyName("is_colosseum_period")] public bool IsColosseumPeriod { get; set; }
[JsonPropertyName("is_round_period")] public bool IsRoundPeriod { get; set; }
[JsonPropertyName("is_normal_two_pick")] public string IsNormalTwoPick { get; set; } = "";
[JsonPropertyName("is_special_mode")] public string IsSpecialMode { get; set; } = "";
[JsonPropertyName("is_all_card_enabled")] public int IsAllCardEnabled { get; set; }
[JsonPropertyName("sales_period_info")] public JsonElement SalesPeriodInfo { get; set; }
}

View 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; }
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class DefaultDeckSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("class_id")] public int ClassId { get; set; }
[JsonPropertyName("sleeve_id")] public long SleeveId { get; set; }
[JsonPropertyName("leader_skin_id")] public int LeaderSkinId { get; set; }
[JsonPropertyName("deck_name")] public string DeckName { get; set; } = "";
[JsonPropertyName("card_id_array")] public List<long> CardIdArray { get; set; } = new();
}

View File

@@ -0,0 +1,16 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors one entry of <c>seeds/feature-maintenances.json</c>. Source: <c>/load/index
/// data.feature_maintenance_list</c> (array of dicts; usually empty). <see cref="Data"/> is
/// the raw element so it round-trips verbatim into the entity's jsonb column.
/// </summary>
public sealed class FeatureMaintenanceSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("feature_key")] public string FeatureKey { get; set; } = "";
[JsonPropertyName("data")] public JsonElement Data { get; set; }
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors one entry of <c>seeds/loading-exclusion-cards.json</c>. Source: <c>/load/index
/// data.loading_exclusion_card_list</c> (array of card_ids).
/// </summary>
public sealed class LoadingExclusionCardSeed
{
[JsonPropertyName("card_id")] public long CardId { get; set; }
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors one entry of <c>seeds/maintenance-cards.json</c>. Source: <c>/load/index
/// data.maintenance_card_list</c> (array of card_ids; usually empty).
/// </summary>
public sealed class MaintenanceCardSeed
{
[JsonPropertyName("card_id")] public long CardId { get; set; }
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class MasterPointRankingPeriodSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("period_num")] public int PeriodNum { get; set; }
[JsonPropertyName("necessary_score")] public long NecessaryScore { get; set; }
[JsonPropertyName("begin_time")] public string BeginTime { get; set; } = "";
[JsonPropertyName("end_time")] public string EndTime { get; set; } = "";
}

View 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; }
}

View 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; } = "[]";
}

View File

@@ -0,0 +1,53 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class PackSeed
{
[JsonPropertyName("parent_gacha_id")] public int ParentGachaId { get; set; }
[JsonPropertyName("base_pack_id")] public int BasePackId { get; set; }
[JsonPropertyName("gacha_type")] public int GachaType { get; set; }
[JsonPropertyName("pack_category")] public int PackCategory { get; set; }
[JsonPropertyName("poster_type")] public int PosterType { get; set; }
[JsonPropertyName("commence_date")] public string CommenceDate { get; set; } = "";
[JsonPropertyName("complete_date")] public string CompleteDate { get; set; } = "";
[JsonPropertyName("sleeve_id")] public int SleeveId { get; set; }
[JsonPropertyName("special_sleeve_id")] public int SpecialSleeveId { get; set; }
[JsonPropertyName("override_draw_effect_pack_id")] public int OverrideDrawEffectPackId { get; set; }
[JsonPropertyName("override_ui_effect_pack_id")] public int OverrideUiEffectPackId { get; set; }
[JsonPropertyName("gacha_detail")] public string GachaDetail { get; set; } = "";
[JsonPropertyName("is_hide")] public bool IsHide { get; set; }
[JsonPropertyName("is_new")] public bool IsNew { get; set; }
[JsonPropertyName("is_pre_release")] public bool IsPreRelease { get; set; }
[JsonPropertyName("open_count_limit")] public int OpenCountLimit { get; set; }
[JsonPropertyName("sales_period_time")] public string? SalesPeriodTime { get; set; }
[JsonPropertyName("gacha_point")] public PackGachaPointSeed? GachaPoint { get; set; }
[JsonPropertyName("child_gachas")] public List<PackChildGachaSeed> ChildGachas { get; set; } = new();
[JsonPropertyName("banners")] public List<PackBannerSeed> Banners { get; set; } = new();
}
public sealed class PackGachaPointSeed
{
[JsonPropertyName("exchangeable_point")] public int ExchangeablePoint { get; set; }
[JsonPropertyName("increase_gacha_point")] public int IncreaseGachaPoint { get; set; }
}
public sealed class PackChildGachaSeed
{
[JsonPropertyName("gacha_id")] public int GachaId { get; set; }
[JsonPropertyName("type_detail")] public int TypeDetail { get; set; }
[JsonPropertyName("cost")] public int Cost { get; set; }
[JsonPropertyName("card_count")] public int CardCount { get; set; } = 8;
[JsonPropertyName("item_id")] public long? ItemId { get; set; }
[JsonPropertyName("is_daily_single")] public bool IsDailySingle { get; set; }
[JsonPropertyName("override_increase_gacha_point")] public int OverrideIncreaseGachaPoint { get; set; }
[JsonPropertyName("purchase_limit_count")] public int PurchaseLimitCount { get; set; }
[JsonPropertyName("free_gacha_campaign_id")] public int? FreeGachaCampaignId { get; set; }
[JsonPropertyName("campaign_name")] public string? CampaignName { get; set; }
}
public sealed class PackBannerSeed
{
[JsonPropertyName("banner_name")] public string BannerName { get; set; } = "";
[JsonPropertyName("dialog_title")] public string DialogTitle { get; set; } = "";
}

View File

@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class PaymentItemSeed
{
[JsonPropertyName("record_id")] public int RecordId { get; set; }
[JsonPropertyName("product_id")] public int ProductId { get; set; }
[JsonPropertyName("store_product_id")] public long StoreProductId { get; set; }
[JsonPropertyName("name")] public string Name { get; set; } = "";
[JsonPropertyName("text")] public string Text { get; set; } = "";
[JsonPropertyName("price")] public string Price { get; set; } = "0";
[JsonPropertyName("charge_crystal_num")] public int ChargeCrystalNum { get; set; }
[JsonPropertyName("free_crystal_num")] public int FreeCrystalNum { get; set; }
[JsonPropertyName("purchase_limit")] public int PurchaseLimit { get; set; }
[JsonPropertyName("special_shop_flag")] public int SpecialShopFlag { get; set; }
[JsonPropertyName("image_name")] public string ImageName { get; set; } = "";
[JsonPropertyName("start_time")] public string StartTime { get; set; } = "";
[JsonPropertyName("end_time")] public string EndTime { get; set; } = "";
[JsonPropertyName("remaining_time")] public int RemainingTime { get; set; }
[JsonPropertyName("is_resale_product")] public int IsResaleProduct { get; set; }
[JsonPropertyName("resale_start_date")] public string ResaleStartDate { get; set; } = "";
}

View File

@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class PracticeOpponentSeed
{
[JsonPropertyName("practice_id")] public int PracticeId { get; set; }
[JsonPropertyName("text_id")] public string TextId { get; set; } = "";
[JsonPropertyName("class_id")] public int ClassId { get; set; }
[JsonPropertyName("chara_id")] public int CharaId { get; set; }
[JsonPropertyName("degree_id")] public int DegreeId { get; set; }
[JsonPropertyName("ai_deck_level")] public int AiDeckLevel { get; set; }
[JsonPropertyName("ai_logic_level")] public int AiLogicLevel { get; set; }
[JsonPropertyName("ai_max_life")] public int AiMaxLife { get; set; }
[JsonPropertyName("battle3dfield_id")] public string Battle3dFieldId { get; set; } = "1";
[JsonPropertyName("is_maintenance")] public bool IsMaintenance { get; set; }
[JsonPropertyName("is_campaign_practice")] public bool IsCampaignPractice { get; set; }
}

View 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; }
}

View File

@@ -0,0 +1,14 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class PuzzleGroupSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("basic_title_text_id")] public string BasicTitleTextId { get; set; } = "";
[JsonPropertyName("puzzle_chara_id")] public int PuzzleCharaId { get; set; }
[JsonPropertyName("chara_id")] public int CharaId { get; set; }
[JsonPropertyName("sort_type")] public int SortType { get; set; }
[JsonPropertyName("difficulty_name_list")] public JsonElement DifficultyNameList { get; set; }
}

View File

@@ -0,0 +1,17 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class PuzzleMissionSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("mission_name")] public string MissionName { get; set; } = "";
[JsonPropertyName("achieved_message")] public string AchievedMessage { get; set; } = "";
[JsonPropertyName("require_number")] public int RequireNumber { get; set; }
[JsonPropertyName("campaign_commence_time")] public long CampaignCommenceTime { get; set; }
[JsonPropertyName("order_id")] public int OrderId { get; set; }
[JsonPropertyName("reward_type")] public int RewardType { get; set; }
[JsonPropertyName("reward_detail_id")] public long RewardDetailId { get; set; }
[JsonPropertyName("reward_number")] public int RewardNumber { get; set; }
[JsonPropertyName("target_puzzle_group_id")] public int? TargetPuzzleGroupId { get; set; }
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class PuzzleSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("group_id")] public int GroupId { get; set; }
[JsonPropertyName("puzzle_difficulty")] public int PuzzleDifficulty { get; set; }
[JsonPropertyName("is_additional")] public bool IsAdditional { get; set; }
[JsonPropertyName("is_playable")] public bool IsPlayable { get; set; }
[JsonPropertyName("release_condition_text_id")] public string ReleaseConditionTextId { get; set; } = "";
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors one entry of <c>seeds/reprinted-cards.json</c>. Source: <c>/load/index
/// data.reprinted_base_card_ids</c> (dict or list of card_ids).
/// </summary>
public sealed class ReprintedCardSeed
{
[JsonPropertyName("card_id")] public long CardId { get; set; }
}

View 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; } = "";
}

View File

@@ -0,0 +1,19 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class SealedSeasonSeed
{
[JsonPropertyName("id")] public int Id { get; set; } = 1;
[JsonPropertyName("enable")] public int Enable { get; set; }
[JsonPropertyName("crystal_cost")] public int CrystalCost { get; set; }
[JsonPropertyName("rupy_cost")] public int RupyCost { get; set; }
[JsonPropertyName("ticket_cost")] public int TicketCost { get; set; }
[JsonPropertyName("deck_using_num_min")] public int DeckUsingNumMin { get; set; }
[JsonPropertyName("schedule_id")] public int ScheduleId { get; set; }
[JsonPropertyName("is_join")] public bool IsJoin { get; set; }
[JsonPropertyName("is_deck_code_maintenance")] public bool IsDeckCodeMaintenance { get; set; }
[JsonPropertyName("pack_info")] public JsonElement PackInfo { get; set; }
[JsonPropertyName("sales_period_info")] public JsonElement SalesPeriodInfo { get; set; }
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
public sealed class SpecialDeckFormatSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("deck_format")] public string DeckFormat { get; set; } = "";
[JsonPropertyName("end_time")] public string EndTime { get; set; } = "";
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors one entry of <c>seeds/spot-cards.json</c>. Source: <c>/load/index data.spot_cards</c>
/// — extractor reshapes the wire dict {card_id: cost} into a list of {card_id, cost} rows.
/// </summary>
public sealed class SpotCardSeed
{
[JsonPropertyName("card_id")] public long CardId { get; set; }
[JsonPropertyName("cost")] public int Cost { get; set; }
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors one entry of <c>seeds/unlimited-restrictions.json</c>. Source: <c>/load/index
/// data.unlimited_restricted_base_card_id_list</c> (dict {card_id: restriction_value}).
/// </summary>
public sealed class UnlimitedRestrictionSeed
{
[JsonPropertyName("card_id")] public long CardId { get; set; }
[JsonPropertyName("restriction_value")] public int RestrictionValue { get; set; }
}

View File

@@ -34,7 +34,7 @@ public static class Program
Console.WriteLine($"[Bootstrap] Connection: {RedactPassword(opts.ConnectionString)}"); Console.WriteLine($"[Bootstrap] Connection: {RedactPassword(opts.ConnectionString)}");
Console.WriteLine($"[Bootstrap] Reference CSVs: {opts.ReferenceDataDir}"); Console.WriteLine($"[Bootstrap] Reference CSVs: {opts.ReferenceDataDir}");
Console.WriteLine($"[Bootstrap] Cards file: {opts.CardsFile}"); Console.WriteLine($"[Bootstrap] Cards file: {opts.CardsFile}");
Console.WriteLine($"[Bootstrap] Captures: {opts.CapturesDir}"); Console.WriteLine($"[Bootstrap] Seeds: {opts.SeedDir}");
var dbOptions = new DbContextOptionsBuilder<SVSimDbContext>() var dbOptions = new DbContextOptionsBuilder<SVSimDbContext>()
.UseNpgsql(opts.ConnectionString) .UseNpgsql(opts.ConnectionString)
@@ -43,7 +43,7 @@ public static class Program
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions); await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
// Bootstrap applies pending migrations first — migrations are now DDL-only, all data // Bootstrap applies pending migrations first — migrations are now DDL-only, all data
// (reference tables, cards, card cosmetic rewards, prod-captured globals, game config) // (reference tables, cards, card cosmetic rewards, per-table seed globals, game config)
// is loaded by importers below. This means a freshly migrated DB is structure-only; // is loaded by importers below. This means a freshly migrated DB is structure-only;
// every importer is idempotent so re-running is safe. // every importer is idempotent so re-running is safe.
Console.WriteLine("[Bootstrap] Applying pending migrations..."); Console.WriteLine("[Bootstrap] Applying pending migrations...");
@@ -75,14 +75,46 @@ public static class Program
if (!opts.SkipGlobals) if (!opts.SkipGlobals)
{ {
await new GlobalsImporter().ImportAllAsync(context, opts.CapturesDir); // Per-domain seed pipeline. Each importer reads a per-table JSON seed file under
// SVSim.Bootstrap/Data/seeds/ produced by an extractor in data_dumps/extract/.
//
// RotationConfigImporter writes the Rotation GameConfig section that RotationFlagUpdater
// reads; CardImporter ran earlier in the !SkipCards block so CardSets are populated.
await new RotationConfigImporter().ImportAsync(context, opts.SeedDir);
await new MyRotationImporter().ImportAsync(context, opts.SeedDir);
await new AvatarAbilityImporter().ImportAsync(context, opts.SeedDir);
await new ArenaSeasonImporter().ImportAsync(context, opts.SeedDir);
await new BattlePassImporter().ImportAsync(context, opts.SeedDir);
await new BattlePassSeasonImporter().ImportAsync(context, opts.SeedDir);
await new BattlePassRewardImporter().ImportAsync(context, opts.SeedDir);
await new DailyLoginBonusImporter().ImportAsync(context, opts.SeedDir);
await new PreReleaseInfoImporter().ImportAsync(context, opts.SeedDir);
await new CardListsImporter().ImportAsync(context, opts.SeedDir);
await new RotationFlagUpdater().UpdateAsync(context);
await new PracticeOpponentImporter().ImportAsync(context, opts.SeedDir);
await new PaymentItemImporter().ImportAsync(context, opts.SeedDir);
var puzzleImporter = new PuzzleImporter();
await puzzleImporter.ImportGroupsAsync(context, opts.SeedDir);
await puzzleImporter.ImportPuzzlesAsync(context, opts.SeedDir);
await puzzleImporter.ImportMissionsAsync(context, opts.SeedDir);
var mypage = new MyPageGlobalsImporter();
await mypage.ImportBannersAsync(context, opts.SeedDir);
await mypage.ImportColosseumAsync(context, opts.SeedDir);
await mypage.ImportSealedAsync(context, opts.SeedDir);
await mypage.ImportMasterPointRankingPeriodAsync(context, opts.SeedDir);
await mypage.ImportSpecialDeckFormatsAsync(context, opts.SeedDir);
await new DefaultDeckImporter().ImportAsync(context, opts.SeedDir);
await new PackImporter().ImportAsync(context, opts.SeedDir);
// BuildDeck pipeline: series CSV → catalog JSON → package CSV. Catalog must run after // BuildDeck pipeline: series CSV → catalog JSON → package CSV. Catalog must run after
// series CSV (FK on products → series) and before package CSV (so the catalog-side // series CSV (FK on products → series) and before package CSV (so the catalog-side
// enriched rows take precedence over stub creation). // enriched rows take precedence over stub creation).
var buildDeck = new BuildDeckImporter(); var buildDeck = new BuildDeckImporter();
await buildDeck.ImportSeriesAsync(context, opts.ReferenceDataDir); await buildDeck.ImportSeriesAsync(context, opts.ReferenceDataDir);
await buildDeck.ImportCatalogAsync(context, opts.CapturesDir); await buildDeck.ImportCatalogAsync(context, opts.SeedDir);
await buildDeck.ImportPackageAsync(context, opts.ReferenceDataDir); await buildDeck.ImportPackageAsync(context, opts.ReferenceDataDir);
} }
else else
@@ -106,7 +138,6 @@ public static class Program
private static BootstrapOptions? ParseArgs(string[] args) private static BootstrapOptions? ParseArgs(string[] args)
{ {
string? cards = null; string? cards = null;
string? captures = null;
string? referenceDataDir = null; string? referenceDataDir = null;
string? connection = null; string? connection = null;
bool skipReference = false; bool skipReference = false;
@@ -122,7 +153,6 @@ public static class Program
switch (a) switch (a)
{ {
case "--cards": cards = NextArg(args, ref i); break; case "--cards": cards = NextArg(args, ref i); break;
case "--captures": captures = NextArg(args, ref i); break;
case "--reference-data-dir": referenceDataDir = NextArg(args, ref i); break; case "--reference-data-dir": referenceDataDir = NextArg(args, ref i); break;
case "--connection-string": connection = NextArg(args, ref i); break; case "--connection-string": connection = NextArg(args, ref i); break;
case "--skip-reference": skipReference = true; break; case "--skip-reference": skipReference = true; break;
@@ -140,26 +170,25 @@ public static class Program
} }
// All bootstrap inputs ship in-project under SVSim.Bootstrap/Data/, copied next to the // All bootstrap inputs ship in-project under SVSim.Bootstrap/Data/, copied next to the
// binary on build. The --cards/--captures/--reference-data-dir flags are ad-hoc overrides // binary on build. The --cards/--reference-data-dir flags are ad-hoc overrides
// (e.g. point at a fresh loader dump before promoting it into the project). // (e.g. point at a fresh loader dump before promoting it into the project).
string baseDir = AppContext.BaseDirectory; string baseDir = AppContext.BaseDirectory;
string shippedDataDir = Path.Combine(baseDir, "Data"); string shippedDataDir = Path.Combine(baseDir, "Data");
string shippedCaptures = Path.Combine(shippedDataDir, "prod-captures");
string shippedCardsFile = Path.Combine(shippedDataDir, "cards.json"); string shippedCardsFile = Path.Combine(shippedDataDir, "cards.json");
string cardsFile = cards ?? positionalCards ?? shippedCardsFile; string cardsFile = cards ?? positionalCards ?? shippedCardsFile;
string capturesDir = captures ?? shippedCaptures;
string refDir = referenceDataDir ?? shippedDataDir; string refDir = referenceDataDir ?? shippedDataDir;
string shippedStoryDir = Path.Combine(shippedDataDir, "story"); string shippedStoryDir = Path.Combine(shippedDataDir, "story");
string storyDir = storyDataDir ?? shippedStoryDir; string storyDir = storyDataDir ?? shippedStoryDir;
string shippedSeedDir = Path.Combine(shippedDataDir, "seeds");
string connStr = connection string connStr = connection
?? Environment.GetEnvironmentVariable("NPGSQL_CONNECTION") ?? Environment.GetEnvironmentVariable("NPGSQL_CONNECTION")
?? DefaultConnectionString; ?? DefaultConnectionString;
return new BootstrapOptions( return new BootstrapOptions(
cardsFile, capturesDir, refDir, connStr, skipReference, skipCards, skipGlobals, cardsFile, refDir, connStr, skipReference, skipCards, skipGlobals,
skipStory, storyDir); skipStory, storyDir, shippedSeedDir);
} }
private static string NextArg(string[] args, ref int i) private static string NextArg(string[] args, ref int i)
@@ -181,28 +210,30 @@ public static class Program
" loader dump) — promote into Data/ when you're ready to make it permanent.\n" + " loader dump) — promote into Data/ when you're ready to make it permanent.\n" +
"\n" + "\n" +
" --cards <file> Override path to cards.json (default: shipped Data/cards.json)\n" + " --cards <file> Override path to cards.json (default: shipped Data/cards.json)\n" +
" --captures <dir> Override path to prod-captures directory\n" +
" (default: shipped Data/prod-captures)\n" +
" --reference-data-dir <dir> Override reference CSV directory (default: shipped Data/)\n" + " --reference-data-dir <dir> Override reference CSV directory (default: shipped Data/)\n" +
" --connection-string <conn> Postgres connection (or NPGSQL_CONNECTION env var,\n" + " --connection-string <conn> Postgres connection (or NPGSQL_CONNECTION env var,\n" +
$" then \"{DefaultConnectionString}\")\n" + $" then \"{DefaultConnectionString}\")\n" +
" --skip-reference Skip reference-data import (classes, sleeves, ranks, ...)\n" + " --skip-reference Skip reference-data import (classes, sleeves, ranks, ...)\n" +
" --skip-cards Skip card + card-cosmetic-reward import\n" + " --skip-cards Skip card + card-cosmetic-reward import\n" +
" --skip-globals Skip prod-captured globals import\n" + " --skip-globals Skip seed-driven globals import (per-table JSON under Data/seeds)\n" +
" --story-data-dir <dir> Override story data directory (default: shipped Data/story)\n" + " --story-data-dir <dir> Override story data directory (default: shipped Data/story)\n" +
" --skip-story Skip story import (worlds/sections/chapters/sbs)\n" + " --skip-story Skip story import (worlds/sections/chapters/sbs)\n" +
"\n" + "\n" +
"Capture-derived seeds are produced by extractors under data_dumps/extract/* and\n" +
"checked into SVSim.Bootstrap/Data/seeds/. The bootstrap project never parses wire\n" +
"captures directly — refresh seeds by re-running the relevant extractor.\n" +
"\n" +
"Back-compat: `svsim-bootstrap <cards.json> [connection]` still works (positional)."); "Back-compat: `svsim-bootstrap <cards.json> [connection]` still works (positional).");
} }
private sealed record BootstrapOptions( private sealed record BootstrapOptions(
string CardsFile, string CardsFile,
string CapturesDir,
string ReferenceDataDir, string ReferenceDataDir,
string ConnectionString, string ConnectionString,
bool SkipReference, bool SkipReference,
bool SkipCards, bool SkipCards,
bool SkipGlobals, bool SkipGlobals,
bool SkipStory, bool SkipStory,
string StoryDataDir); string StoryDataDir,
string SeedDir);
} }

View File

@@ -10,9 +10,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Data\prod-captures\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Data\*.csv"> <Content Include="Data\*.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -27,6 +24,9 @@
<Content Include="Data\story\*.json"> <Content Include="Data\story\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Data\seeds\**\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,11 @@
namespace SVSim.Database.Enums;
/// <summary>
/// Reward track on a battle pass season. Wire shape uses the section keys
/// <c>"normal"</c> and <c>"premium"</c> under <c>reward_info</c>.
/// </summary>
public enum BattlePassTrack
{
Normal = 0,
Premium = 1,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SVSim.Database.Migrations
{
/// <inheritdoc />
public partial class RefactorBattlePassLevels : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "RewardData", table: "BattlePassLevels");
migrationBuilder.AddColumn<int>(
name: "RequiredPoint", table: "BattlePassLevels",
type: "integer", nullable: false, defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "RequiredPoint", table: "BattlePassLevels");
migrationBuilder.AddColumn<string>(
name: "RewardData", table: "BattlePassLevels",
type: "jsonb", nullable: false, defaultValue: "{}");
}
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More