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 SVSim.Bootstrap.Models.Seed;
using SVSim.Database;
using SVSim.Database.Models;
using static SVSim.Bootstrap.Importers.ImporterBase;
namespace SVSim.Bootstrap.Importers;
/// <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):
/// 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,
/// creates disabled stubs for products not seeded by the catalog importer
/// Idempotent — re-runnable on the same files.
@@ -133,14 +133,13 @@ public class BuildDeckImporter
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");
if (data is null) return 0;
var seed = SeedLoader.LoadList<BuildDeckCatalogSeed>(Path.Combine(seedDir, "build-deck-catalog.json"));
if (seed.Count == 0) return 0;
int touchedSeries = 0, touchedProducts = 0;
// Load existing rows for fast lookup
var existingSeries = await db.BuildDeckSeries
.Include(s => s.SeriesRewards)
.ToDictionaryAsync(s => s.Id);
@@ -148,126 +147,75 @@ public class BuildDeckImporter
.Include(p => p.Rewards)
.ToDictionaryAsync(p => p.Id);
// The captured data root is an object keyed by order_id string ("15"…"21"); iterate values.
foreach (var seriesNode in data.Value.EnumerateObject())
foreach (var s in seed)
{
var s = seriesNode.Value;
int seriesId = GetInt(s, "series_id");
int orderId = GetInt(s, "order_id");
bool isNew = GetBool(s, "is_new");
if (s.SeriesId == 0) continue;
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
// CSV should already exist. If not (e.g. the capture has a series the CSV doesn't),
// create a bare row so the FK from products holds.
// Catalog typically runs after the series CSV; if a seed series isn't in the
// CSV we create a bare stub so the FK from products holds.
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,
};
db.BuildDeckSeries.Add(seriesRow);
existingSeries[seriesId] = seriesRow;
existingSeries[s.SeriesId] = seriesRow;
}
seriesRow.OrderIndex = orderId;
seriesRow.IsNew = isNew;
seriesRow.OrderIndex = s.OrderId;
seriesRow.IsNew = s.IsNew;
seriesRow.IsEnabled = true;
// Series rewards: replace wholesale (capture is authoritative for enabled series)
seriesRow.SeriesRewards.Clear();
if (s.TryGetProperty("series_rewards", out var seriesRewards) &&
seriesRewards.ValueKind == JsonValueKind.Object)
foreach (var r in s.SeriesRewards)
{
foreach (var tier in seriesRewards.EnumerateObject())
seriesRow.SeriesRewards.Add(new BuildDeckSeriesRewardEntry
{
if (!int.TryParse(tier.Name, out int tierIndex)) continue;
if (!tier.Value.TryGetProperty("reward_list", out var rewardList) ||
rewardList.ValueKind != JsonValueKind.Array) continue;
int itemIndex = 0;
foreach (var r in rewardList.EnumerateArray())
{
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"),
});
}
}
TierIndex = r.TierIndex,
ItemIndex = r.ItemIndex,
RewardType = r.RewardType,
RewardDetailId = r.RewardDetailId,
RewardNumber = r.RewardNumber,
MessageId = r.MessageId,
});
}
touchedSeries++;
// 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())
foreach (var p in s.Products)
{
int productId = GetInt(p, "product_id");
if (!existingProducts.TryGetValue(productId, out var productRow))
if (!existingProducts.TryGetValue(p.ProductId, out var productRow))
{
productRow = new BuildDeckProductEntry { Id = productId, SeriesId = seriesId };
productRow = new BuildDeckProductEntry { Id = p.ProductId, SeriesId = s.SeriesId };
db.BuildDeckProducts.Add(productRow);
existingProducts[productId] = productRow;
existingProducts[p.ProductId] = productRow;
}
productRow.SeriesId = seriesId;
productRow.LeaderId = GetInt(p, "leader_id");
productRow.DeckCode = GetString(p, "deck_code");
productRow.ProductNameKey = GetString(p, "product_name");
productRow.FeaturedCardId = GetLong(p, "featured_card_id");
productRow.PurchaseNumMax = GetInt(p, "purchase_num_max");
productRow.SeriesId = s.SeriesId;
productRow.LeaderId = p.LeaderId;
productRow.DeckCode = p.DeckCode;
productRow.ProductNameKey = p.ProductName;
productRow.FeaturedCardId = p.FeaturedCardId;
productRow.PurchaseNumMax = p.PurchaseNumMax;
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();
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;
productRow.Rewards.Add(new BuildDeckProductRewardEntry
{
RewardIndex = idx,
RewardType = GetInt(r.Value, "reward_type"),
RewardDetailId = GetLong(r.Value, "reward_detail_id"),
RewardNumber = GetInt(r.Value, "reward_number"),
MessageId = GetInt(r.Value, "message_id"),
});
}
RewardIndex = r.RewardIndex,
RewardType = r.RewardType,
RewardDetailId = r.RewardDetailId,
RewardNumber = r.RewardNumber,
MessageId = r.MessageId,
});
}
capturedThisSeries.Add(productRow);
touchedProducts++;
}
// Second pass: backfill missing tier per-series when sibling products share a unique value.
BackfillSeriesTier(capturedThisSeries);
}
await db.SaveChangesAsync();
@@ -275,63 +223,6 @@ public class BuildDeckImporter
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>
/// Maps a product_id to its series_id using the numeric pattern derived from the /info capture
/// 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;
/// <summary>
/// Shared helpers for content importers. Loads a prod-capture JSON file by endpoint name from
/// a captures directory, returning the inner <c>data</c> element. Picks the latest matching dated
/// file (e.g. <c>load-index-2026-05-23.json</c>) if multiple exist for the same endpoint.
/// Tiny shared helper for content importers. Capture parsing has moved out of the bootstrap
/// project entirely (extractors under <c>data_dumps/extract/</c> emit per-table seed JSON);
/// only the wire-date normaliser stays here because several seed-driven importers still need
/// to canonicalise prod-shaped timestamp strings.
/// </summary>
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>
public static DateTime ParseWireDateTime(string? s)
{
@@ -91,50 +20,4 @@ public static class ImporterBase
}
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] Reference CSVs: {opts.ReferenceDataDir}");
Console.WriteLine($"[Bootstrap] Cards file: {opts.CardsFile}");
Console.WriteLine($"[Bootstrap] Captures: {opts.CapturesDir}");
Console.WriteLine($"[Bootstrap] Seeds: {opts.SeedDir}");
var dbOptions = new DbContextOptionsBuilder<SVSimDbContext>()
.UseNpgsql(opts.ConnectionString)
@@ -43,7 +43,7 @@ public static class Program
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
// 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;
// every importer is idempotent so re-running is safe.
Console.WriteLine("[Bootstrap] Applying pending migrations...");
@@ -75,14 +75,46 @@ public static class Program
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
// series CSV (FK on products → series) and before package CSV (so the catalog-side
// enriched rows take precedence over stub creation).
var buildDeck = new BuildDeckImporter();
await buildDeck.ImportSeriesAsync(context, opts.ReferenceDataDir);
await buildDeck.ImportCatalogAsync(context, opts.CapturesDir);
await buildDeck.ImportCatalogAsync(context, opts.SeedDir);
await buildDeck.ImportPackageAsync(context, opts.ReferenceDataDir);
}
else
@@ -106,7 +138,6 @@ public static class Program
private static BootstrapOptions? ParseArgs(string[] args)
{
string? cards = null;
string? captures = null;
string? referenceDataDir = null;
string? connection = null;
bool skipReference = false;
@@ -122,7 +153,6 @@ public static class Program
switch (a)
{
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 "--connection-string": connection = NextArg(args, ref i); 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
// 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).
string baseDir = AppContext.BaseDirectory;
string shippedDataDir = Path.Combine(baseDir, "Data");
string shippedCaptures = Path.Combine(shippedDataDir, "prod-captures");
string shippedCardsFile = Path.Combine(shippedDataDir, "cards.json");
string cardsFile = cards ?? positionalCards ?? shippedCardsFile;
string capturesDir = captures ?? shippedCaptures;
string refDir = referenceDataDir ?? shippedDataDir;
string shippedStoryDir = Path.Combine(shippedDataDir, "story");
string storyDir = storyDataDir ?? shippedStoryDir;
string shippedSeedDir = Path.Combine(shippedDataDir, "seeds");
string connStr = connection
?? Environment.GetEnvironmentVariable("NPGSQL_CONNECTION")
?? DefaultConnectionString;
return new BootstrapOptions(
cardsFile, capturesDir, refDir, connStr, skipReference, skipCards, skipGlobals,
skipStory, storyDir);
cardsFile, refDir, connStr, skipReference, skipCards, skipGlobals,
skipStory, storyDir, shippedSeedDir);
}
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" +
"\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" +
" --connection-string <conn> Postgres connection (or NPGSQL_CONNECTION env var,\n" +
$" then \"{DefaultConnectionString}\")\n" +
" --skip-reference Skip reference-data import (classes, sleeves, ranks, ...)\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" +
" --skip-story Skip story import (worlds/sections/chapters/sbs)\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).");
}
private sealed record BootstrapOptions(
string CardsFile,
string CapturesDir,
string ReferenceDataDir,
string ConnectionString,
bool SkipReference,
bool SkipCards,
bool SkipGlobals,
bool SkipStory,
string StoryDataDir);
string StoryDataDir,
string SeedDir);
}

View File

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