Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions forge-core/src/main/java/forge/deck/Deck.java
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,31 @@ public boolean equals(final Object o) {
return false;
}

/**
* Replace this deck's contents with those from another deck.
* All sections and metadata are copied.
* @param other the deck to copy from
*/
public void copyFrom(Deck other) {
// Clear all current sections
for (DeckSection section : DeckSection.values()) {
CardPool pool = this.get(section);
if (pool != null) {
pool.clear();
}
}
// Copy all sections from other
for (Entry<DeckSection, CardPool> entry : other.parts.entrySet()) {
this.getOrCreate(entry.getKey()).addAll(entry.getValue());
}
// Copy metadata
this.setName(other.getName());
this.setAiHints(StringUtils.join(other.aiHints, " | "));
this.setDraftNotes(other.draftNotes);
this.tags.clear();
this.tags.addAll(other.tags);
}

public static int getAverageCMC(Deck deck) {
int totalCMC = 0;
int totalCount = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package forge.screens.deckeditor;

import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.toolbox.FCheckBox;
import forge.util.Localizer;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

public class ColorSelectionDialog extends JDialog {
private final List<FCheckBox> colorBoxes = new ArrayList<>();
private ColorSet selectedColors = ColorSet.fromMask(0x1F); // All colors by default
private boolean confirmed = false;

public ColorSelectionDialog(Window owner, ColorSet defaultSelected) {
super(owner, Localizer.getInstance().getMessage("lblChooseColors"), ModalityType.APPLICATION_MODAL);
setLayout(new BorderLayout());
getContentPane().setBackground(Color.WHITE); // Set dialog background
JPanel panel = new JPanel(new GridLayout(0, 1));
panel.setBackground(Color.WHITE); // Set panel background
String[] colorNames = {"White", "Blue", "Black", "Red", "Green"};
Color[] fgColors = {Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK};
byte[] colorMasks = {forge.card.MagicColor.WHITE, forge.card.MagicColor.BLUE, forge.card.MagicColor.BLACK, forge.card.MagicColor.RED, forge.card.MagicColor.GREEN};
for (int i = 0; i < colorNames.length; i++) {
boolean selected = defaultSelected == null || defaultSelected.hasAnyColor(colorMasks[i]);
FCheckBox box = new FCheckBox(colorNames[i], selected);
box.setForeground(fgColors[i]); // Set label text color to black
box.setBackground(Color.WHITE); // Set checkbox background
colorBoxes.add(box);
panel.add(box);
}
add(panel, BorderLayout.CENTER);
JPanel btnPanel = new JPanel();
btnPanel.setBackground(Color.WHITE); // Set button panel background
JButton ok = new JButton("OK");
JButton cancel = new JButton("Cancel");
btnPanel.add(ok);
btnPanel.add(cancel);
add(btnPanel, BorderLayout.SOUTH);
ok.addActionListener(e -> {
confirmed = true;
selectedColors = ColorSet.fromMask(
(colorBoxes.get(0).isSelected() ? MagicColor.WHITE : 0) |
(colorBoxes.get(1).isSelected() ? MagicColor.BLUE : 0) |
(colorBoxes.get(2).isSelected() ? MagicColor.BLACK : 0) |
(colorBoxes.get(3).isSelected() ? MagicColor.RED : 0) |
(colorBoxes.get(4).isSelected() ? MagicColor.GREEN : 0)
);
setVisible(false);
});
cancel.addActionListener(e -> setVisible(false));
pack();
setLocationRelativeTo(owner);
}

public boolean isConfirmed() {
return confirmed;
}

public ColorSet getSelectedColors() {
return selectedColors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,24 @@
*/
package forge.screens.deckeditor.controllers;

import java.awt.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Supplier;

import forge.card.CardEdition;
import forge.card.ColorSet;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckGroup;
import forge.deck.DeckSection;
import forge.game.GameType;
import forge.gamemodes.limited.DeckColors;
import forge.gamemodes.limited.LimitedDeckBuilder;
import forge.gui.UiCommand;
import forge.gui.framework.DragCell;
import forge.gui.framework.FScreen;
Expand All @@ -38,6 +43,7 @@
import forge.itemmanager.ItemManagerConfig;
import forge.model.FModel;
import forge.screens.deckeditor.AddBasicLandsDialog;
import forge.screens.deckeditor.ColorSelectionDialog;
import forge.screens.deckeditor.SEditorIO;
import forge.screens.deckeditor.views.VAllDecks;
import forge.screens.deckeditor.views.VBrawlDecks;
Expand All @@ -52,6 +58,8 @@
import forge.toolbox.FComboBox;
import forge.util.storage.IStorage;

import javax.swing.*;

/**
* Child controller for limited deck editor UI.
*
Expand Down Expand Up @@ -114,6 +122,8 @@ public CEditorLimited(final IStorage<DeckGroup> deckMap0, final FScreen screen0,
DeckSection ds = (DeckSection)cb.getSelectedItem();
setEditorMode(ds);
});

VCurrentDeck.SINGLETON_INSTANCE.getBtnAutoBuildLimited().setCommand(() -> onAutoBuildLimitedDeck());
}

//========== Overridden from ACEditorBase
Expand Down Expand Up @@ -287,4 +297,97 @@ public void resetUIChanges() {
tinyLeadersDecksParent.addDoc(VTinyLeadersDecks.SINGLETON_INSTANCE);
}
}

private ColorSet getMostCommonColors() {
// Gather all cards from sideboard and main deck (the pool)
List<PaperCard> pool = new ArrayList<>();
pool.addAll(getHumanDeck().getOrCreate(DeckSection.Sideboard).toFlatList());
pool.addAll(getHumanDeck().getMain().toFlatList());
int[] colorCounts = new int[5]; // WUBRG order
for (PaperCard card : pool) {
ColorSet cs = card.getRules().getColor();
if (cs.hasWhite()) colorCounts[0]++;
if (cs.hasBlue()) colorCounts[1]++;
if (cs.hasBlack()) colorCounts[2]++;
if (cs.hasRed()) colorCounts[3]++;
if (cs.hasGreen()) colorCounts[4]++;
}
// Find the two most common colors
int first = -1, second = -1;
for (int i = 0; i < 5; i++) {
if (first == -1 || colorCounts[i] > colorCounts[first]) {
second = first;
first = i;
} else if (second == -1 || colorCounts[i] > colorCounts[second]) {
second = i;
}
}
byte[] colorMasks = {forge.card.MagicColor.WHITE, forge.card.MagicColor.BLUE, forge.card.MagicColor.BLACK, forge.card.MagicColor.RED, forge.card.MagicColor.GREEN};
int mask = 0;
if (first != -1 && colorCounts[first] > 0) mask |= colorMasks[first];
if (second != -1 && colorCounts[second] > 0) mask |= colorMasks[second];
if (mask == 0) mask = 0x1F; // fallback: all colors
return ColorSet.fromMask(mask);
}

private void onAutoBuildLimitedDeck() {
// Show dialog to ask user for colors
Window window = SwingUtilities.getWindowAncestor(VCurrentDeck.SINGLETON_INSTANCE.getPnlHeader());
ColorSet defaultColors = getMostCommonColors();
ColorSelectionDialog dialog = new ColorSelectionDialog(window, defaultColors);
dialog.setVisible(true);
if (!dialog.isConfirmed()) {
return;
}
ColorSet chosenColors = dialog.getSelectedColors();
// Build deck using LimitedDeckBuilder with forHuman=true
List<PaperCard> pool = new ArrayList<>();
// Gather all cards from sideboard and main deck (the pool)
pool.addAll(getHumanDeck().getOrCreate(DeckSection.Sideboard).toFlatList());
pool.addAll(getHumanDeck().getMain().toFlatList());
DeckColors deckColors = new DeckColors();
java.util.List<Byte> colorBytes = new ArrayList<>();
for (forge.card.MagicColor.Color c : chosenColors.getOrderedColors()) {
colorBytes.add(c.getColorMask());
}
deckColors.setColorsByList(colorBytes);
LimitedDeckBuilder builder = new LimitedDeckBuilder(pool, deckColors, true);
Deck newDeck = builder.buildDeck();

// Move cards via UI methods
CardPool currentMain = getHumanDeck().getMain();
CardPool generatedMain = newDeck.getMain();

// 1. Remove cards from Main that are not in generatedMain (move to sideboard)
List<Entry<PaperCard, Integer>> toRemove = new ArrayList<>();
for (Entry<PaperCard, Integer> entry : currentMain) {
int inGenerated = generatedMain.count(entry.getKey());
int inCurrent = entry.getValue();
if (inGenerated < inCurrent) {
toRemove.add(Map.entry(entry.getKey(), inCurrent - inGenerated));
}
}
for (Entry<PaperCard, Integer> entry : toRemove) {
List<Entry<PaperCard, Integer>> single = List.of(entry);
onRemoveItems(single, false); // move from main to sideboard
}

// 2. Add cards to Main that are in generatedMain but not enough in currentMain (move from sideboard)
List<Entry<PaperCard, Integer>> toAdd = new ArrayList<>();
for (Entry<PaperCard, Integer> entry : generatedMain) {
int inCurrent = currentMain.count(entry.getKey());
int inGenerated = entry.getValue();
if (inGenerated > inCurrent) {
toAdd.add(Map.entry(entry.getKey(), inGenerated - inCurrent));
}
}
for (Entry<PaperCard, Integer> entry : toAdd) {
List<Entry<PaperCard, Integer>> single = List.of(entry);
onAddItems(single, false); // move from sideboard to main
}

// UI will update via onAddItems/onRemoveItems
resetTables();
getDeckController().notifyModelChanged();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public enum VCurrentDeck implements IVDoc<CCurrentDeck> {
.tooltip(localizer.getMessage("ttImportDeck"))
.opaque(true).hoverable(true).build();

private final FLabel btnAutoBuildLimited = new FLabel.Builder()
.fontSize(14)
.text(localizer.getMessage("lblAutoBuildLimited"))
.tooltip("Auto-build a limited deck for a human player")
.opaque(true).hoverable(true).build();

private final FTextField txfTitle = new FTextField.Builder().ghostText("[" + localizer.getMessage("lblNewDeck") +"]").build();

private final JPanel pnlHeader = new JPanel();
Expand All @@ -104,6 +110,7 @@ public enum VCurrentDeck implements IVDoc<CCurrentDeck> {
pnlHeader.add(btnSaveAs, "w 26px!, h 26px!");
pnlHeader.add(btnPrintProxies, "w 26px!, h 26px!");
pnlHeader.add(btnImport, "w 61px!, h 26px!");
pnlHeader.add(btnAutoBuildLimited, "w 120px!, h 26px!"); // Add new button near import
}

//========== Overridden from IVDoc
Expand Down Expand Up @@ -197,7 +204,7 @@ public FLabel getBtnNew() {
return btnNew;
}

/** @return {@link forge.gui.toolbar.FTextField} */
/** @return {@link forge.toolbox.FTextField} */
public FTextField getTxfTitle() {
return txfTitle;
}
Expand All @@ -214,4 +221,9 @@ public JPanel getPnlHeader() {
public FLabel getBtnImport() {
return btnImport;
}

/** @return {@link javax.swing.JLabel} */
public FLabel getBtnAutoBuildLimited() {
return btnAutoBuildLimited;
}
}
6 changes: 4 additions & 2 deletions forge-gui/res/languages/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3131,7 +3131,7 @@ lblRevealFaceDownCards=Revealing face-down cards from
lblLearnALesson=Learn a Lesson
lblSpeed=SPEED: {0}
lblMaxSpeed=SPEED: MAX!
lblCrank=CRANK! {0}
lblCrank=CRANK! ? {0}
#QuestPreferences.java
lblWildOpponentNumberError=Wild Opponents can only be 0 to 3
#GauntletWinLose.java
Expand Down Expand Up @@ -3373,4 +3373,6 @@ lblDataMigrationMsg=Data Migration completed!\nPlease check your Inventory and E
#AdventureDeckEditor.java
lblRemoveUnsupportedCard=Remove unsupported card
lblRemoveAllUnsupportedCards=Unsupported cards have been removed from your inventory.
lbldisableCrackedItems=Disable the possibility of your items breaking after losing a boss fight.
lbldisableCrackedItems=Disable the possibility of your items breaking after losing a boss fight.
lblAutoBuildLimited=Quick Build
lblChooseColors=Choose Colors
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class DeckColors {

public int MAX_COLORS = 2;

DeckColors() {}
public DeckColors() {}

DeckColors(int max_col) {
// If we want to draft decks that are more than 2 colors, we can change the MAX_COLORS value here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ protected final float getSpellPercentage() {
protected final List<String> setsWithBasicLands = new ArrayList<>();
protected List<PaperCard> rankedColorList;
protected final List<PaperCard> draftedConspiracies;
protected final boolean forHuman;

// Views for aiPlayable
private Iterable<PaperCard> onColorCreatures;
Expand All @@ -69,18 +70,26 @@ protected final float getSpellPercentage() {
* Cards to build the deck from.
* @param pClrs
* Chosen colors.
* @param forHuman
* True if building for a human player, false for AI.
*/
public LimitedDeckBuilder(final List<PaperCard> dList, final DeckColors pClrs) {
public LimitedDeckBuilder(final List<PaperCard> dList, final DeckColors pClrs, final boolean forHuman) {
super(FModel.getMagicDb().getCommonCards(), DeckFormat.Limited);
this.availableList = dList;
this.deckColors = pClrs;
this.colors = pClrs.getChosenColors();
this.forHuman = forHuman;

// remove Unplayables
this.aiPlayables = availableList.stream()
if (forHuman) {
// For humans, all cards are playables
this.aiPlayables = new ArrayList<>(availableList);
} else {
// remove Unplayables for AI
this.aiPlayables = availableList.stream()
.filter(PaperCardPredicates.fromRules(CardRulesPredicates.IS_KEPT_IN_AI_LIMITED_DECKS))
.collect(Collectors.toList());
this.availableList.removeAll(aiPlayables);
this.availableList.removeAll(aiPlayables);
}

// keep Conspiracies in a separate list
this.draftedConspiracies = aiPlayables.stream()
Expand All @@ -92,13 +101,17 @@ public LimitedDeckBuilder(final List<PaperCard> dList, final DeckColors pClrs) {
}

/**
* Constructor.
*
* @param list
* Cards to build the deck from.
* Constructor for backward compatibility (AI by default).
*/
public LimitedDeckBuilder(final List<PaperCard> dList, final DeckColors pClrs) {
this(dList, pClrs, false);
}

/**
* Constructor for backward compatibility (AI by default).
*/
public LimitedDeckBuilder(final List<PaperCard> list) {
this(list, new DeckColors());
this(list, new DeckColors(), false);
}

@Override
Expand Down