From 9cbc746ba64396a33e99b76d9f538e5800d063ee Mon Sep 17 00:00:00 2001 From: Noone Date: Thu, 28 May 2026 00:01:10 -0400 Subject: [PATCH 1/2] feat(adventure): Add deck import/export and inventory management to Adventure mode Adds multi-format deck import (Arena, MTGO, native .dck), collection export in Arena format, and a mark-for-sale workflow. Available via both console commands and new UI buttons in the deck selection screen. Console commands: - load deck Import deck, auto-give missing cards - load deck buy Import deck, purchase missing cards with gold - check deck Report which cards are missing - save deck Export selected deck as .dck - export collection Export full collection in Arena format - mark sell Mark listed cards for auto-sell UI (DeckSelectScene): - Import Deck button with 3-mode dialog (free/buy/check-only) - Export Deck, Export Collection, Mark for Sale buttons Cross-platform file path handling (Unix/Mac/Windows) with ~ expansion. Co-authored-by: Cursor --- .../adventure/player/AdventurePlayer.java | 34 +++ .../adventure/scene/DeckSelectScene.java | 140 +++++++++++ .../stage/ConsoleCommandInterpreter.java | 59 +++++ .../src/forge/adventure/util/CardUtil.java | 237 ++++++++++++++++++ 4 files changed, 470 insertions(+) diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index c7cfba8e1bf..b1d33bb3fa6 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -1587,6 +1587,40 @@ public int copyDeck() { return -1; } + /** + * Finds the first empty deck slot, expanding if needed. + * Returns -1 only if all 99 slots are occupied. + */ + public int findFirstEmptySlot() { + for (int i = 0; i < maxDeckCount; i++) { + if (i >= getDeckCount()) addDeck(); + if (isEmptyDeck(i)) return i; + } + if (getDeckCount() < 99) { + maxDeckCount = Math.min(maxDeckCount + 1, 99); + addDeck(); + return getDeckCount() - 1; + } + return -1; + } + + /** + * Replaces the contents of a deck slot with an imported deck. + * Clears all existing sections and copies from the source. + */ + public void importIntoSlot(int slot, Deck importedDeck) { + Deck target = getDeck(slot); + for (DeckSection section : DeckSection.values()) { + if (target.has(section)) target.get(section).clear(); + } + for (java.util.Map.Entry entry : importedDeck) { + target.getOrCreate(entry.getKey()).addAll(entry.getValue()); + } + if (importedDeck.getName() != null && !importedDeck.getName().isEmpty()) { + target.setName(importedDeck.getName()); + } + } + private void ensureDeckLoadoutsSize() { while (deckLoadouts.size() < getDeckCount()) { deckLoadouts.add(null); diff --git a/forge-gui-mobile/src/forge/adventure/scene/DeckSelectScene.java b/forge-gui-mobile/src/forge/adventure/scene/DeckSelectScene.java index d2dfad086c0..75bf04bb77f 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DeckSelectScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DeckSelectScene.java @@ -11,14 +11,20 @@ import forge.Forge; import forge.adventure.player.AdventurePlayer; import forge.adventure.stage.GameHUD; +import forge.adventure.util.CardUtil; import forge.adventure.util.Controls; import forge.adventure.util.Current; +import forge.deck.io.DeckSerializer; +import forge.util.FileUtil; + +import java.io.File; public class DeckSelectScene extends UIScene { private final IntMap buttons = new IntMap<>(); private final IntMap