Deck Importer support for Adventure, Quest, and Planar Conquest (#8681)

* Some cleanup.

* Expanded/fixed basic land set functions for quest and adventure.

* Get land sets from unlocked planes in conquest mode.

* Add importer for Adventure, Quest, and Conquest.

* Remove unused import

* Remove redundant override

* Deprecate hasBasicLands predicate.

* Delete getManaNameAndSymbol

---------

Co-authored-by: Jetz <Jetz722@gmail.com>
This commit is contained in:
Jetz72
2025-09-11 11:22:17 -05:00
committed by GitHub
parent db8e084332
commit 43a1570601
22 changed files with 596 additions and 437 deletions

View File

@@ -1018,16 +1018,13 @@ public final class CardEdition implements Comparable<CardEdition> {
public static final Predicate<CardEdition> HAS_BOOSTER_BOX = edition -> edition.getBoosterBoxCount() > 0;
@Deprecated //Use CardEdition::hasBasicLands and a nonnull test.
public static final Predicate<CardEdition> hasBasicLands = ed -> {
if (ed == null) {
// Happens for new sets with "???" code
return false;
}
for(String landName : MagicColor.Constant.BASIC_LANDS) {
if (null == StaticData.instance().getCommonCards().getCard(landName, ed.getCode(), 0))
return false;
}
return true;
return ed.hasBasicLands();
};
}
@@ -1048,7 +1045,7 @@ public final class CardEdition implements Comparable<CardEdition> {
public boolean hasBasicLands() {
for(String landName : MagicColor.Constant.BASIC_LANDS) {
if (null == StaticData.instance().getCommonCards().getCard(landName, this.getCode(), 0))
if (this.getCardInSet(landName).isEmpty())
return false;
}
return true;

View File

@@ -49,6 +49,16 @@ public class DeckRecognizer {
LIMITED_CARD,
CARD_FROM_NOT_ALLOWED_SET,
CARD_FROM_INVALID_SET,
/**
* Valid card request, but can't be imported because the player does not have enough copies.
* Should be replaced with a different printing if possible.
*/
CARD_NOT_IN_INVENTORY,
/**
* Valid card request for a card that isn't in the player's inventory, but new copies can be acquired freely.
* Usually used for basic lands. Should be supplied to the import controller by the editor.
*/
FREE_CARD_NOT_IN_INVENTORY,
// Warning messages
WARNING_MESSAGE,
UNKNOWN_CARD,
@@ -63,10 +73,14 @@ public class DeckRecognizer {
CARD_TYPE,
CARD_RARITY,
CARD_CMC,
MANA_COLOUR
MANA_COLOUR;
public static final EnumSet<TokenType> CARD_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET, CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY);
public static final EnumSet<TokenType> IN_DECK_TOKEN_TYPES = EnumSet.of(LEGAL_CARD, LIMITED_CARD, DECK_NAME, FREE_CARD_NOT_IN_INVENTORY);
public static final EnumSet<TokenType> CARD_PLACEHOLDER_TOKEN_TYPES = EnumSet.of(CARD_TYPE, CARD_RARITY, CARD_CMC, MANA_COLOUR);
}
public enum LimitedCardType{
public enum LimitedCardType {
BANNED,
RESTRICTED,
}
@@ -108,6 +122,10 @@ public class DeckRecognizer {
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
}
public static Token NotInInventoryFree(final PaperCard card, final int count, final DeckSection section) {
return new Token(TokenType.FREE_CARD_NOT_IN_INVENTORY, count, card, section, true);
}
// WARNING MESSAGES
// ================
public static Token UnknownCard(final String cardName, final String setCode, final int count) {
@@ -126,6 +144,10 @@ public class DeckRecognizer {
return new Token(TokenType.WARNING_MESSAGE, msg);
}
public static Token NotInInventory(final PaperCard card, final int count, final DeckSection section) {
return new Token(TokenType.CARD_NOT_IN_INVENTORY, count, card, section, false);
}
/* =================================
* DECK SECTIONS
* ================================= */
@@ -239,14 +261,11 @@ public class DeckRecognizer {
/**
* Filters all token types that have a PaperCard instance set (not null)
* @return true for tokens of type:
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET, CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY.
* False otherwise.
*/
public boolean isCardToken() {
return (this.type == TokenType.LEGAL_CARD ||
this.type == TokenType.LIMITED_CARD ||
this.type == TokenType.CARD_FROM_NOT_ALLOWED_SET ||
this.type == TokenType.CARD_FROM_INVALID_SET);
return TokenType.CARD_TOKEN_TYPES.contains(this.type);
}
/**
@@ -255,9 +274,7 @@ public class DeckRecognizer {
* LEGAL_CARD, LIMITED_CARD, DECK_NAME; false otherwise.
*/
public boolean isTokenForDeck() {
return (this.type == TokenType.LEGAL_CARD ||
this.type == TokenType.LIMITED_CARD ||
this.type == TokenType.DECK_NAME);
return TokenType.IN_DECK_TOKEN_TYPES.contains(this.type);
}
/**
@@ -266,7 +283,7 @@ public class DeckRecognizer {
* False otherwise.
*/
public boolean isCardTokenForDeck() {
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
return isCardToken() && isTokenForDeck();
}
/**
@@ -276,10 +293,7 @@ public class DeckRecognizer {
* CARD_RARITY, CARD_CMC, CARD_TYPE, MANA_COLOUR
*/
public boolean isCardPlaceholder(){
return (this.type == TokenType.CARD_RARITY ||
this.type == TokenType.CARD_CMC ||
this.type == TokenType.MANA_COLOUR ||
this.type == TokenType.CARD_TYPE);
return TokenType.CARD_PLACEHOLDER_TOKEN_TYPES.contains(this.type);
}
/** Determines if current token is a Deck Section token
@@ -536,7 +550,7 @@ public class DeckRecognizer {
PaperCard tokenCard = token.getCard();
if (isAllowed(tokenSection)) {
if (!tokenSection.equals(referenceDeckSectionInParsing)) {
if (tokenSection != referenceDeckSectionInParsing) {
Token sectionToken = Token.DeckSection(tokenSection.name(), this.allowedDeckSections);
// just check that last token is stack is a card placeholder.
// In that case, add the new section token before the placeholder
@@ -575,7 +589,7 @@ public class DeckRecognizer {
refLine = purgeAllLinks(refLine);
String line;
if (StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER))
if (refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER))
line = refLine.replaceAll(LINE_COMMENT_DELIMITER_OR_MD_HEADER, "");
else
line = refLine.trim(); // Remove any trailing formatting
@@ -584,7 +598,7 @@ public class DeckRecognizer {
// Final fantasy cards like Summon: Choco/Mog should be ommited to be recognized. TODO: fix maybe for future cards
if (!line.contains("Summon:"))
line = SEARCH_SINGLE_SLASH.matcher(line).replaceFirst(" // ");
if (StringUtils.startsWith(line, ASTERISK)) // markdown lists (tappedout md export)
if (line.startsWith(ASTERISK)) // Markdown lists (tappedout md export)
line = line.substring(2);
// == Patches to Corner Cases
@@ -600,8 +614,8 @@ public class DeckRecognizer {
Token result = recogniseCardToken(line, referenceSection);
if (result == null)
result = recogniseNonCardToken(line);
return result != null ? result : StringUtils.startsWith(refLine, DOUBLE_SLASH) ||
StringUtils.startsWith(refLine, LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
return result != null ? result : refLine.startsWith(DOUBLE_SLASH) ||
refLine.startsWith(LINE_COMMENT_DELIMITER_OR_MD_HEADER) ?
new Token(TokenType.COMMENT, 0, refLine) : new Token(TokenType.UNKNOWN_TEXT, 0, refLine);
}
@@ -613,7 +627,7 @@ public class DeckRecognizer {
while (m.find()) {
line = line.replaceAll(m.group(), "").trim();
}
if (StringUtils.endsWith(line, "()"))
if (line.endsWith("()"))
return line.substring(0, line.length()-2);
return line;
}
@@ -741,21 +755,12 @@ public class DeckRecognizer {
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand
private DeckSection getTokenSection(String deckSec, DeckSection currentDeckSection, PaperCard card){
if (deckSec != null) {
DeckSection cardSection;
switch (deckSec.toUpperCase().trim()) {
case "MB":
cardSection = DeckSection.Main;
break;
case "SB":
cardSection = DeckSection.Sideboard;
break;
case "CM":
cardSection = DeckSection.Commander;
break;
default:
cardSection = DeckSection.matchingSection(card);
break;
}
DeckSection cardSection = switch (deckSec.toUpperCase().trim()) {
case "MB" -> DeckSection.Main;
case "SB" -> DeckSection.Sideboard;
case "CM" -> DeckSection.Commander;
default -> DeckSection.matchingSection(card);
};
if (cardSection.validate(card))
return cardSection;
}
@@ -1017,51 +1022,21 @@ public class DeckRecognizer {
private static MagicColor.Color getMagicColor(String colorName){
if (colorName.toLowerCase().startsWith("multi") || colorName.equalsIgnoreCase("m"))
return null; // will be handled separately
byte color = MagicColor.fromName(colorName.toLowerCase());
switch (color) {
case MagicColor.WHITE:
return MagicColor.Color.WHITE;
case MagicColor.BLUE:
return MagicColor.Color.BLUE;
case MagicColor.BLACK:
return MagicColor.Color.BLACK;
case MagicColor.RED:
return MagicColor.Color.RED;
case MagicColor.GREEN:
return MagicColor.Color.GREEN;
default:
return MagicColor.Color.COLORLESS;
}
return MagicColor.Color.fromByte(MagicColor.fromName(colorName.toLowerCase()));
}
public static String getLocalisedMagicColorName(String colorName){
Localizer localizer = Localizer.getInstance();
switch(colorName.toLowerCase()){
case MagicColor.Constant.WHITE:
return localizer.getMessage("lblWhite");
case MagicColor.Constant.BLUE:
return localizer.getMessage("lblBlue");
case MagicColor.Constant.BLACK:
return localizer.getMessage("lblBlack");
case MagicColor.Constant.RED:
return localizer.getMessage("lblRed");
case MagicColor.Constant.GREEN:
return localizer.getMessage("lblGreen");
case MagicColor.Constant.COLORLESS:
return localizer.getMessage("lblColorless");
case "multicolour":
case "multicolor":
return localizer.getMessage("lblMulticolor");
default:
return "";
}
return switch (colorName.toLowerCase()) {
case MagicColor.Constant.WHITE -> localizer.getMessage("lblWhite");
case MagicColor.Constant.BLUE -> localizer.getMessage("lblBlue");
case MagicColor.Constant.BLACK -> localizer.getMessage("lblBlack");
case MagicColor.Constant.RED -> localizer.getMessage("lblRed");
case MagicColor.Constant.GREEN -> localizer.getMessage("lblGreen");
case MagicColor.Constant.COLORLESS -> localizer.getMessage("lblColorless");
case "multicolour", "multicolor" -> localizer.getMessage("lblMulticolor");
default -> "";
};
}
/**
@@ -1080,37 +1055,6 @@ public class DeckRecognizer {
return "";
}
private static Pair<String, String> getManaNameAndSymbol(String matchedMana) {
if (matchedMana == null)
return null;
Localizer localizer = Localizer.getInstance();
switch (matchedMana.toLowerCase()) {
case MagicColor.Constant.WHITE:
case "w":
return Pair.of(localizer.getMessage("lblWhite"), MagicColor.Color.WHITE.getSymbol());
case MagicColor.Constant.BLUE:
case "u":
return Pair.of(localizer.getMessage("lblBlue"), MagicColor.Color.BLUE.getSymbol());
case MagicColor.Constant.BLACK:
case "b":
return Pair.of(localizer.getMessage("lblBlack"), MagicColor.Color.BLACK.getSymbol());
case MagicColor.Constant.RED:
case "r":
return Pair.of(localizer.getMessage("lblRed"), MagicColor.Color.RED.getSymbol());
case MagicColor.Constant.GREEN:
case "g":
return Pair.of(localizer.getMessage("lblGreen"), MagicColor.Color.GREEN.getSymbol());
case MagicColor.Constant.COLORLESS:
case "c":
return Pair.of(localizer.getMessage("lblColorless"), MagicColor.Color.COLORLESS.getSymbol());
default: // Multicolour
return Pair.of(localizer.getMessage("lblMulticolor"), "");
}
}
public static boolean isDeckName(final String lineAsIs) {
if (lineAsIs == null)
return false;

View File

@@ -68,7 +68,7 @@ public class AddBasicLandsDialog {
private static final int LAND_PANEL_PADDING = 3;
private final FComboBoxPanel<CardEdition> cbLandSet = new FComboBoxPanel<>(Localizer.getInstance().getMessage("lblLandSet") + ":", FlowLayout.CENTER,
IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition.Predicates.hasBasicLands));
IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition::hasBasicLands));
private final MainPanel panel = new MainPanel();
private final LandPanel pnlPlains = new LandPanel("Plains");

View File

@@ -46,7 +46,6 @@ import forge.toolbox.*;
import forge.util.Localizer;
import forge.view.FDialog;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.StringUtils;
import static forge.deck.DeckRecognizer.TokenType.*;
@@ -523,7 +522,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
else
deck.setName(currentDeckName);
}
host.getDeckController().loadDeck(deck, controller.getCreateNewDeck());
host.getDeckController().loadDeck(deck, controller.getImportBehavior() != DeckImportController.ImportBehavior.MERGE);
processWindowEvent(new WindowEvent(DeckImport.this, WindowEvent.WINDOW_CLOSING));
});
@@ -531,7 +530,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
this.createNewDeckCheckbox.setSelected(false);
this.createNewDeckCheckbox.addActionListener(e -> {
boolean createNewDeck = createNewDeckCheckbox.isSelected();
controller.setCreateNewDeck(createNewDeck);
controller.setImportBehavior(createNewDeck ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
String cmdAcceptLabel = createNewDeck ? CREATE_NEW_DECK_CMD_LABEL : IMPORT_CARDS_CMD_LABEL;
cmdAcceptButton.setText(cmdAcceptLabel);
String smartCardArtChboxTooltip = createNewDeck ? SMART_CARDART_TT_NO_DECK : SMART_CARDART_TT_WITH_DECK;
@@ -600,7 +599,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
if (token.getType() == LIMITED_CARD)
cssClass = WARN_MSG_CLASS;
String statusMsg = String.format("<span class=\"%s\" style=\"font-size: 9px;\">%s</span>", cssClass,
getTokenStatusMessage(token));
controller.getTokenStatusMessage(token));
statusLbl.append(statusMsg);
}
@@ -740,12 +739,12 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
private String toHTML(final DeckRecognizer.Token token) {
if (token == null)
return "";
String tokenMsg = getTokenMessage(token);
String tokenMsg = controller.getTokenMessage(token);
if (tokenMsg == null)
return "";
String tokenStatus = getTokenStatusMessage(token);
String tokenStatus = controller.getTokenStatusMessage(token);
String cssClass = getTokenCSSClass(token.getType());
if (tokenStatus.length() == 0)
if (tokenStatus.isEmpty())
tokenMsg = padEndWithHTMLSpaces(tokenMsg, 2*PADDING_TOKEN_MSG_LENGTH+10);
else {
tokenMsg = padEndWithHTMLSpaces(tokenMsg, PADDING_TOKEN_MSG_LENGTH);
@@ -755,11 +754,6 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
tokenMsg = String.format("<a class=\"%s\" href=\"%s\">%s</a>", cssClass,
token.getKey().toString(), tokenMsg);
if (tokenStatus == null) {
String tokenTag = String.format("<td colspan=\"2\" class=\"%s\">%s</td>", cssClass, tokenMsg);
return String.format("<tr>%s</tr>", tokenTag);
}
String tokenTag = "<td class=\"%s\">%s</td>";
String tokenMsgTag = String.format(tokenTag, cssClass, tokenMsg);
String tokenStatusTag;
@@ -776,97 +770,6 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
return String.format("%s%s", targetMsg, spacer);
}
private String getTokenMessage(DeckRecognizer.Token token) {
switch (token.getType()) {
case LEGAL_CARD:
case LIMITED_CARD:
case CARD_FROM_NOT_ALLOWED_SET:
case CARD_FROM_INVALID_SET:
return String.format("%s x %s %s", token.getQuantity(), token.getText(), getTokenFoilLabel(token));
// Card Warning Msgs
case UNKNOWN_CARD:
case UNSUPPORTED_CARD:
return token.getQuantity() > 0 ? String.format("%s x %s", token.getQuantity(), token.getText())
: token.getText();
case UNSUPPORTED_DECK_SECTION:
return String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
Localizer.getInstance()
.getMessage("lblWarnDeckSectionNotAllowedInEditor", token.getText(),
this.currentGameType));
// Special Case of Card moved into another section (e.g. Commander from Sideboard)
case WARNING_MESSAGE:
return String.format("%s: %s", Localizer.getInstance()
.getMessage("lblWarningMsgPrefix"), token.getText());
// Placeholders
case DECK_SECTION_NAME:
return String.format("%s: %s", Localizer.getInstance().getMessage("lblDeckSection"),
token.getText());
case CARD_RARITY:
return String.format("%s: %s", Localizer.getInstance().getMessage("lblRarity"),
token.getText());
case CARD_TYPE:
case CARD_CMC:
case MANA_COLOUR:
case COMMENT:
return token.getText();
case DECK_NAME:
return String.format("%s: %s", Localizer.getInstance().getMessage("lblDeckName"),
token.getText());
case UNKNOWN_TEXT:
default:
return null;
}
}
private String getTokenStatusMessage(DeckRecognizer.Token token){
if (token == null)
return "";
switch (token.getType()) {
case LIMITED_CARD:
return String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
Localizer.getInstance().getMessage("lblWarnLimitedCard",
StringUtils.capitalize(token.getLimitedCardType().name()), getGameFormatLabel()));
case CARD_FROM_NOT_ALLOWED_SET:
return Localizer.getInstance().getMessage("lblErrNotAllowedCard", getGameFormatLabel());
case CARD_FROM_INVALID_SET:
return Localizer.getInstance().getMessage("lblErrCardEditionDate");
case UNSUPPORTED_CARD:
return Localizer.getInstance().getMessage("lblErrUnsupportedCard", this.currentGameType);
case UNKNOWN_CARD:
return String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
Localizer.getInstance().getMessage("lblWarnUnknownCardMsg"));
case UNSUPPORTED_DECK_SECTION:
case WARNING_MESSAGE:
case COMMENT:
case CARD_CMC:
case MANA_COLOUR:
case CARD_TYPE:
case DECK_SECTION_NAME:
case CARD_RARITY:
case DECK_NAME:
case LEGAL_CARD:
case UNKNOWN_TEXT:
default:
return "";
}
}
private String getTokenCSSClass(DeckRecognizer.TokenType tokenType){
switch (tokenType){
case LEGAL_CARD:
@@ -899,17 +802,6 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
return "";
}
}
private String getTokenFoilLabel(DeckRecognizer.Token token) {
if (!token.isCardToken())
return "";
final String foilMarker = "- (Foil)";
return token.getCard().isFoil() ? foilMarker : "";
}
private String getGameFormatLabel() {
return String.format("\"%s\"", this.controller.getCurrentGameFormatName());
}
}
class GameFormatDropdownRenderer extends JLabel implements ListCellRenderer<GameFormat> {

View File

@@ -27,8 +27,6 @@ import forge.item.PaperCard;
import forge.itemmanager.*;
import forge.itemmanager.filters.CardColorFilter;
import forge.itemmanager.filters.CardTypeFilter;
import forge.localinstance.properties.ForgePreferences;
import forge.menu.FCheckBoxMenuItem;
import forge.menu.FDropDownMenu;
import forge.menu.FMenuItem;
import forge.menu.FPopupMenu;
@@ -41,6 +39,7 @@ import forge.util.Utils;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class AdventureDeckEditor extends FDeckEditor {
protected static class AdventureEditorConfig extends DeckEditorConfig {
@@ -146,7 +145,8 @@ public class AdventureDeckEditor extends FDeckEditor {
if(event.cardBlock != null) {
if(event.cardBlock.getLandSet() != null)
return List.of(event.cardBlock.getLandSet());
List<CardEdition> eventSets = event.cardBlock.getSets();
List<CardEdition> eventSets = new ArrayList<>(event.cardBlock.getSets());
eventSets.removeIf(Predicate.not(CardEdition::hasBasicLands));
if(!eventSets.isEmpty())
return eventSets;
}
@@ -558,7 +558,7 @@ public class AdventureDeckEditor extends FDeckEditor {
currentEvent.participants[i].setDeck(opponentDecks[i]);
}
currentEvent.draftedDeck = (Deck) currentEvent.registeredDeck.copyTo("Draft Deck");
if (allowsAddBasic()) {
if (allowAddBasic()) {
showAddBasicLandsDialog();
//Might be annoying if you haven't pruned your deck yet, but best to remind player that
//this probably needs to be done since it's there since it's not normally part of Adventure
@@ -713,27 +713,6 @@ public class AdventureDeckEditor extends FDeckEditor {
return this.deckHeader;
}
@Override
protected FPopupMenu createMoreOptionsMenu() {
return new FPopupMenu() {
@Override
protected void buildMenu() {
Localizer localizer = Forge.getLocalizer();
addItem(new FMenuItem(localizer.getMessage("btnCopyToClipboard"), Forge.hdbuttons ? FSkinImage.HDEXPORT : FSkinImage.BLANK, e1 -> FDeckViewer.copyDeckToClipboard(getDeck())));
if (allowsAddBasic()) {
FMenuItem addBasic = new FMenuItem(localizer.getMessage("lblAddBasicLands"), FSkinImage.LANDLOGO, e1 -> showAddBasicLandsDialog());
addItem(addBasic);
}
if(FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.DEV_MODE_ENABLED)) {
addItem(new FCheckBoxMenuItem(localizer.getMessage("cbEnforceDeckLegality"), shouldEnforceConformity(), e -> toggleConformity()));
String devSuffix = " (" + localizer.getMessage("lblDev") + ")";
addItem(new FMenuItem(localizer.getMessage("lblAddcard") + devSuffix, FSkinImage.HDPLUS, e -> showDevAddCardDialog()));
}
((DeckEditorPage) getSelectedPage()).buildDeckMenu(this);
}
};
}
@Override
protected void addChosenBasicLands(CardPool landsToAdd) {
if(isLimitedEditor())
@@ -765,6 +744,12 @@ public class AdventureDeckEditor extends FDeckEditor {
catalog.moveCards(landsToMove, getMainDeckPage());
}
@Override
protected PaperCard supplyPrintForImporter(PaperCard missingCard) {
PaperCard out = super.supplyPrintForImporter(missingCard);
return out == null ? null : out.getNoSellVersion();
}
@Override
protected void cacheTabPages() {
super.cacheTabPages();
@@ -775,7 +760,9 @@ public class AdventureDeckEditor extends FDeckEditor {
}
@Override
protected boolean allowsAddBasic() {
protected boolean allowAddBasic() {
if(getEditorConfig() instanceof DeckPreviewConfig)
return false;
AdventureEventData currentEvent = getCurrentEvent();
if (currentEvent == null)
return true;

View File

@@ -59,7 +59,7 @@ public class AddBasicLandsDialog extends FDialog {
private final Consumer<CardPool> callback;
private final FLabel lblLandSet = add(new FLabel.Builder().text(Forge.getLocalizer().getMessage("lblLandSet") + ":").font(FSkinFont.get(12)).textColor(FLabel.getInlineLabelColor()).build());
private final FComboBox<CardEdition> cbLandSet = add(new FComboBox<>(IterableUtil.filter(StaticData.instance().getEditions(), CardEdition.Predicates.hasBasicLands)));
private final FComboBox<CardEdition> cbLandSet = add(new FComboBox<>(IterableUtil.filter(StaticData.instance().getSortedEditions(), CardEdition::hasBasicLands)));
private final FScrollPane scroller = add(new FScrollPane() {
@Override

View File

@@ -81,13 +81,21 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
public boolean allowsCardReplacement() { return hasInfiniteCardPool() || usePlayerInventory(); }
public List<CardEdition> getBasicLandSets(Deck currentDeck) {
if(hasInfiniteCardPool())
return FModel.getMagicDb().getSortedEditions().stream().filter(CardEdition::hasBasicLands).collect(Collectors.toList());
return List.of(DeckProxy.getDefaultLandSet(currentDeck));
}
protected abstract IDeckController getController();
protected abstract DeckEditorPage[] getInitialPages();
protected DeckSection[] getExtraSections() {
public DeckSection[] getPrimarySections() {
if(getGameType() != null)
return getGameType().getPrimaryDeckSections().toArray(new DeckSection[0]);
return new DeckSection[]{DeckSection.Main, DeckSection.Sideboard};
}
public DeckSection[] getExtraSections() {
if(getGameType() != null)
return getGameType().getSupplimentalDeckSections().toArray(new DeckSection[0]);
return new DeckSection[]{DeckSection.Attractions, DeckSection.Contraptions};
@@ -144,7 +152,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
ItemManagerConfig catalogConfig = null;
ItemManagerConfig mainSectionConfig = null;
ItemManagerConfig sideboardConfig = null;
Function<Deck, CardEdition> fnGetBasicLandSet = null;
Function<Deck, Collection<CardEdition>> fnGetBasicLandSet = null;
Supplier<ItemPool<PaperCard>> itemPoolSupplier = null;
String catalogCaption = null;
@@ -196,7 +204,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
this.sideboardConfig = sideboardConfig;
return this;
}
public GameTypeDeckEditorConfig setBasicLandSetFunction(Function<Deck, CardEdition> fnGetBasicLandSet) {
public GameTypeDeckEditorConfig setBasicLandSetFunction(Function<Deck, Collection<CardEdition>> fnGetBasicLandSet) {
this.fnGetBasicLandSet = fnGetBasicLandSet;
return this;
}
@@ -296,9 +304,21 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
}
@Override
protected DeckSection[] getExtraSections() {
public DeckSection[] getPrimarySections() {
return gameType.getPrimaryDeckSections().toArray(new DeckSection[0]);
}
@Override
public DeckSection[] getExtraSections() {
return gameType.getSupplimentalDeckSections().toArray(new DeckSection[0]);
}
@Override
public List<CardEdition> getBasicLandSets(Deck currentDeck) {
if(this.fnGetBasicLandSet != null)
return List.copyOf(fnGetBasicLandSet.apply(currentDeck));
return super.getBasicLandSets(currentDeck);
}
}
public static DeckEditorConfig EditorConfigConstructed = new GameTypeDeckEditorConfig(GameType.Constructed,
@@ -348,18 +368,19 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
.setMainSectionConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
.setSideboardConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
.setPlayerInventorySupplier(() -> FModel.getQuest().getCards().getCardpool())
.setBasicLandSetFunction(d -> FModel.getQuest().getDefaultLandSet());
.setBasicLandSetFunction(d -> FModel.getQuest().getAvailableLandSets());
public static DeckEditorConfig EditorConfigQuestCommander = new GameTypeDeckEditorConfig(GameType.QuestCommander, DECK_CONTROLLER_QUEST)
.setCatalogConfig(ItemManagerConfig.QUEST_EDITOR_POOL)
.setMainSectionConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
.setSideboardConfig(ItemManagerConfig.QUEST_DECK_EDITOR)
.setPlayerInventorySupplier(() -> FModel.getQuest().getCards().getCardpool())
.setBasicLandSetFunction(d -> FModel.getQuest().getDefaultLandSet());
.setBasicLandSetFunction(d -> FModel.getQuest().getAvailableLandSets());
public static DeckEditorConfig EditorConfigQuestDraft = new GameTypeDeckEditorConfig(GameType.QuestDraft, DECK_CONTROLLER_QUEST_DRAFT);
public static DeckEditorConfig EditorConfigPlanarConquest = new GameTypeDeckEditorConfig(GameType.PlanarConquest, DECK_CONTROLLER_PLANAR_CONQUEST)
.setCatalogConfig(ItemManagerConfig.CONQUEST_COLLECTION)
.setMainSectionConfig(ItemManagerConfig.CONQUEST_DECK_EDITOR)
.setPlayerInventorySupplier(ConquestUtil::getAvailablePool);
.setPlayerInventorySupplier(ConquestUtil::getAvailablePool)
.setBasicLandSetFunction(ConquestUtil::getBasicLandSets);
protected static DeckSectionPage createPageForExtraSection(DeckSection deckSection, DeckEditorConfig editorConfig) {
CardManager cm = new CardManager(false);
@@ -542,7 +563,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
@Override
protected void buildMenu() {
final Localizer localizer = Forge.getLocalizer();
if (allowsAddBasic())
if (allowAddBasic())
addItem(new FMenuItem(localizer.getMessage("lblAddBasicLands"), FSkinImage.LANDLOGO, e -> showAddBasicLandsDialog()));
if (showAddExtraSectionOption()) {
addItem(new FMenuItem(localizer.getMessage("lblAddDeckSection"), FSkinImage.CHAOS, e -> {
@@ -558,28 +579,41 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
});
}));
}
if (editorConfig.getGameType() != null && editorConfig.hasInfiniteCardPool()) {
if (editorConfig.hasInfiniteCardPool() || editorConfig.usePlayerInventory()) {
addItem(new FMenuItem(localizer.getMessage("lblImportFromClipboard"), Forge.hdbuttons ? FSkinImage.HDIMPORT : FSkinImage.OPEN, e -> {
FDeckImportDialog dialog = new FDeckImportDialog(!deck.isEmpty(), FDeckEditor.this.editorConfig);
FDeckImportDialog dialog = new FDeckImportDialog(deck, FDeckEditor.this.editorConfig);
if(editorConfig.usePlayerInventory())
dialog.setFreePrintConverter(FDeckEditor.this::supplyPrintForImporter);
dialog.setImportBannedCards(!FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY));
dialog.setCallback(importedDeck -> {
if (deck != null && importedDeck.hasName()) {
deck.setName(importedDeck.getName());
setHeaderText(importedDeck.getName());
}
if (dialog.createNewDeck()) {
for (Entry<DeckSection, CardPool> section : importedDeck) {
DeckSectionPage page = getPageForSection(section.getKey());
if (page != null)
page.setCards(section.getValue());
}
} else {
for (Entry<DeckSection, CardPool> section : importedDeck) {
DeckSectionPage page = getPageForSection(section.getKey());
if (page != null)
page.addCards(section.getValue());
}
switch (dialog.getImportBehavior()) {
case REPLACE_CURRENT:
for(DeckSectionPage page : pagesBySection.values()) {
if(importedDeck.has(page.deckSection)) {
page.setCards(importedDeck.get(page.deckSection));
if(hiddenExtraSections.contains(page.deckSection))
showExtraSectionTab(page.deckSection);
}
else
page.setCards(new CardPool());
}
break;
case CREATE_NEW:
deckController.setDeck(importedDeck);
break;
case MERGE:
for (Entry<DeckSection, CardPool> section : importedDeck) {
DeckSectionPage page = getPageForSection(section.getKey());
if (page != null)
page.addCards(section.getValue());
}
}
});
dialog.initParse();
dialog.show();
setSelectedPage(getMainDeckPage()); //select main deck page if needed so main deck if visible below dialog
}));
@@ -643,6 +677,20 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
getMainDeckPage().addCards(landsToAdd);
}
/**
* If a card is missing from a player's inventory while importing a deck, it gets run through here.
* Returning a PaperCard will let unlimited copies of that card be used as a substitute. Returning null
* will leave the card missing from the import.
*/
protected PaperCard supplyPrintForImporter(PaperCard missingCard) {
//Could support dungeons here too? Not that we really use them in the editor...
if(!missingCard.isVeryBasicLand())
return null;
List<CardEdition> basicSets = editorConfig.getBasicLandSets(deck);
String setCode = basicSets.isEmpty() ? "JMP" : basicSets.get(0).getCode();
return FModel.getMagicDb().fetchCard(missingCard.getCardName(), setCode, null);
}
protected boolean shouldEnforceConformity() {
if(FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY))
return true;
@@ -695,6 +743,9 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
showExtraSectionTab(section);
if(pagesBySection.containsKey(section))
setSelectedPage(pagesBySection.get(section));
else if(section == DeckSection.Main && pagesBySection.containsKey(mainDeckPage.deckSection))
//Tried to switch to the Main page in a Planar or Scheme deck.
setSelectedPage(pagesBySection.get(mainDeckPage.deckSection));
}
public void notifyNewControllerModel() {
@@ -1027,7 +1078,7 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
protected boolean allowSaveAs() {
return allowSave() && allowRename();
}
protected boolean allowsAddBasic() {
protected boolean allowAddBasic() {
return !isDrafting();
}

View File

@@ -18,11 +18,12 @@
package forge.deck;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import forge.Forge;
import forge.Graphics;
@@ -31,11 +32,15 @@ import forge.deck.DeckRecognizer.TokenType;
import forge.game.GameType;
import forge.gui.FThreads;
import forge.gui.util.SOptionPane;
import forge.item.PaperCard;
import forge.toolbox.FCheckBox;
import forge.toolbox.FComboBox;
import forge.toolbox.FDialog;
import forge.toolbox.FOptionPane;
import forge.toolbox.FTextArea;
import forge.util.ItemPool;
import forge.util.Localizer;
import org.apache.commons.lang3.StringUtils;
public class FDeckImportDialog extends FDialog {
@@ -45,7 +50,7 @@ public class FDeckImportDialog extends FDialog {
private final FCheckBox newEditionCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblImportLatestVersionCard"), false));
private final FCheckBox dateTimeCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblUseOnlySetsReleasedBefore"), false));
private final FCheckBox smartCardArtCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblUseSmartCardArt"), false));
private final FCheckBox createNewDeckCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblNewDeckCheckbox"), false));
private final FCheckBox createNewDeckCheck = add(new FCheckBox(Forge.getLocalizer().getMessage("lblReplaceDeckCheckbox"), false));
// private final FCheckBox importInDeck = add(new FCheckBox()
/*setting onlyCoreExpCheck to false allow the copied cards to pass the check of deck contents
forge-core\src\main\java\forge\deck\Deck.javaDeck.java starting @ Line 320 which is called by
@@ -57,100 +62,60 @@ public class FDeckImportDialog extends FDialog {
private final FComboBox<String> monthDropdown = add(new FComboBox<>()); //don't need wrappers since skin can't change while this dialog is open
private final FComboBox<Integer> yearDropdown = add(new FComboBox<>());
private final boolean showOptions;
private final boolean currentDeckIsEmpty;
private boolean showOptions;
private final Deck currentDeck;
private boolean createNewDeckControl;
private final DeckImportController controller;
private final FDeckEditor.DeckEditorConfig editorConfig;
private final static ImmutableList<String> importOrCancel = ImmutableList.of(Forge.getLocalizer().getMessage("lblImport"), Forge.getLocalizer().getMessage("lblCancel"));
public FDeckImportDialog(final boolean replacingDeck, final FDeckEditor.DeckEditorConfig editorConfig) {
public FDeckImportDialog(final Deck currentDeck, final FDeckEditor.DeckEditorConfig editorConfig) {
super(Forge.getLocalizer().getMessage("lblImportFromClipboard"), 2);
boolean usingInventory = editorConfig.usePlayerInventory();
boolean replacingDeck = !currentDeck.isEmpty() || usingInventory;
this.currentDeck = currentDeck;
this.editorConfig = editorConfig;
ItemPool<PaperCard> cardPool = editorConfig.getCardPool(false);
controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, replacingDeck);
String contents = Forge.getClipboard().getContents();
if (contents == null)
contents = ""; //prevent NPE
txtInput.setText(contents);
if (editorConfig.allowsCardReplacement()) {
GameType gameType = editorConfig.getGameType();
controller.setGameFormat(gameType);
List<DeckSection> supportedSections = new ArrayList<>();
supportedSections.add(DeckSection.Main);
supportedSections.add(DeckSection.Sideboard);
if (editorConfig.hasCommander())
supportedSections.add(DeckSection.Commander);
supportedSections.addAll(Lists.newArrayList(editorConfig.getExtraSections()));
controller.setAllowedSections(supportedSections);
}
GameType gameType = editorConfig.getGameType();
controller.setGameFormat(gameType);
List<DeckSection> supportedSections = new ArrayList<>();
supportedSections.addAll(List.of(editorConfig.getPrimarySections()));
supportedSections.addAll(List.of(editorConfig.getExtraSections()));
controller.setAllowedSections(supportedSections);
controller.setCurrentDeckInEditor(currentDeck);
if(usingInventory)
controller.setPlayerInventory(cardPool);
onlyCoreExpCheck.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet());
newEditionCheck.setSelected(StaticData.instance().cardArtPreferenceIsLatest());
smartCardArtCheck.setSelected(StaticData.instance().isEnabledCardArtSmartSelection());
createNewDeckCheck.setSelected(replacingDeck);
this.currentDeckIsEmpty = !replacingDeck;
this.createNewDeckControl = replacingDeck;
initButton(0, Forge.getLocalizer().getMessage("lblImport"), e -> FThreads.invokeInBackgroundThread(() -> {
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
if(usingInventory)
controller.setImportBehavior(DeckImportController.ImportBehavior.REPLACE_CURRENT);
else
controller.setImportBehavior(createNewDeckControl ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
//if there are any cards that cannot be imported, let user know this and give them the option to cancel
StringBuilder sb = new StringBuilder();
for (DeckRecognizer.Token token : tokens) {
if (token.getType() == TokenType.CARD_FROM_NOT_ALLOWED_SET
|| token.getType() == TokenType.CARD_FROM_INVALID_SET
|| token.getType() == TokenType.UNKNOWN_CARD
|| token.getType() == TokenType.UNSUPPORTED_CARD) {
if (sb.length() > 0)
sb.append("\n");
sb.append(token.getQuantity()).append(" ").append(token.getText());
}
}
if (sb.length() > 0) {
if (SOptionPane.showOptionDialog(Forge.getLocalizer().getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb, Forge.getLocalizer().getMessage("lblImportRemainingCards"), SOptionPane.INFORMATION_ICON, importOrCancel) == 1) {
return;
}
}
final Deck deck = controller.accept(); //must accept in background thread in case a dialog is shown
if (deck == null) { return; }
FThreads.invokeInEdtLater(() -> {
hide();
if (callback != null)
callback.accept(deck);
});
}));
initButton(0, Forge.getLocalizer().getMessage("lblImport"), e -> FThreads.invokeInBackgroundThread(this::performImport));
initButton(1, Forge.getLocalizer().getMessage("lblCancel"), e -> hide());
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
//ensure at least one known card found on clipboard
for (DeckRecognizer.Token token : tokens) {
if (token.getType() == TokenType.LEGAL_CARD) {
showOptions = true;
dateTimeCheck.setCommand(e -> updateDropDownEnabled());
newEditionCheck.setCommand(e -> setArtPreferenceInController());
onlyCoreExpCheck.setCommand(e -> setArtPreferenceInController());
smartCardArtCheck.setCommand(e -> controller.setSmartCardArtOptimisation(smartCardArtCheck.isSelected()));
createNewDeckCheck.setCommand(e -> {
createNewDeckControl = createNewDeckCheck.isSelected();
controller.setCreateNewDeck(createNewDeckControl);
});
updateDropDownEnabled();
setArtPreferenceInController();
return;
}
}
showOptions = false;
setButtonEnabled(0, false);
txtInput.setText(Forge.getLocalizer().getMessage("lblNoKnownCardsOnClipboard"));
dateTimeCheck.setCommand(e -> updateDropDownEnabled());
newEditionCheck.setCommand(e -> setArtPreferenceInController());
onlyCoreExpCheck.setCommand(e -> setArtPreferenceInController());
smartCardArtCheck.setCommand(e -> controller.setSmartCardArtOptimisation(smartCardArtCheck.isSelected()));
createNewDeckCheck.setCommand(e -> {
createNewDeckControl = createNewDeckCheck.isSelected();
controller.setImportBehavior(createNewDeckControl ? DeckImportController.ImportBehavior.CREATE_NEW : DeckImportController.ImportBehavior.MERGE);
});
setShowOptions(false);
}
private void setArtPreferenceInController() {
@@ -160,16 +125,66 @@ public class FDeckImportDialog extends FDialog {
}
private void updateDropDownEnabled() {
boolean enabled = dateTimeCheck.isSelected();
boolean enabled = dateTimeCheck.isSelected() && this.showOptions;
monthDropdown.setEnabled(enabled);
yearDropdown.setEnabled(enabled);
}
private void setShowOptions(boolean showOptions) {
this.showOptions = showOptions;
dateTimeCheck.setEnabled(showOptions);
newEditionCheck.setEnabled(showOptions);
onlyCoreExpCheck.setEnabled(showOptions);
newEditionCheck.setEnabled(showOptions);
smartCardArtCheck.setEnabled(showOptions);
createNewDeckCheck.setEnabled(showOptions);
updateDropDownEnabled();
}
public void setCallback(Consumer<Deck> callback0){
callback = callback0;
}
public boolean createNewDeck(){ return this.createNewDeckControl; }
public void setFreePrintConverter(Function<PaperCard, PaperCard> freePrintConverter) {
this.controller.setFreePrintConverter(freePrintConverter);
}
public DeckImportController.ImportBehavior getImportBehavior() {
return controller.getImportBehavior();
}
public void setImportBannedCards(boolean importBannedCards) {
controller.importBannedAndRestrictedCards(importBannedCards);
}
public void initParse() {
boolean usingInventory = editorConfig.usePlayerInventory();
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
if (usingInventory)
tokens = controller.constrainTokensToInventory();
else if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
//ensure at least one known card found on clipboard
for (DeckRecognizer.Token token : tokens) {
if (token.getType() == TokenType.LEGAL_CARD || token.getType() == TokenType.FREE_CARD_NOT_IN_INVENTORY) {
if(usingInventory) {
//Settings aren't compatible with player inventories.
setShowOptions(false);
return;
}
setShowOptions(true);
updateDropDownEnabled();
setArtPreferenceInController();
return;
}
}
setButtonEnabled(0, false);
txtInput.setText(Forge.getLocalizer().getMessage("lblNoKnownCardsOnClipboard"));
}
@Override
public void drawOverlay(Graphics g) {
@@ -202,7 +217,7 @@ public class FDeckImportDialog extends FDialog {
yearDropdown.setBounds(x + dropDownWidth + fieldPadding, y, dropDownWidth, h);
y += h + fieldPadding;
if (!this.currentDeckIsEmpty){
if (!this.currentDeck.isEmpty()){
smartCardArtCheck.setBounds(x, y, w/2, h);
createNewDeckCheck.setBounds(x + w/2, y, w/2, h);
} else
@@ -222,4 +237,49 @@ public class FDeckImportDialog extends FDialog {
}
return y;
}
private static final EnumSet<TokenType> MISSING_TOKENS = EnumSet.of(TokenType.CARD_FROM_NOT_ALLOWED_SET,
TokenType.CARD_FROM_INVALID_SET, TokenType.UNKNOWN_CARD, TokenType.UNSUPPORTED_CARD,
TokenType.WARNING_MESSAGE, TokenType.CARD_NOT_IN_INVENTORY);
private void performImport() {
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
if (editorConfig.usePlayerInventory())
tokens = controller.constrainTokensToInventory();
else if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
//if there are any cards that cannot be imported, let user know this and give them the option to cancel
StringBuilder sb = new StringBuilder();
for (DeckRecognizer.Token token : tokens) {
if (MISSING_TOKENS.contains(token.getType())) {
if (!sb.isEmpty())
sb.append("\n");
String message = controller.getTokenMessage(token);
String statusMessage = controller.getTokenStatusMessage(token);
if(!StringUtils.isBlank(statusMessage))
sb.append(String.format("%s - (%s)", message, statusMessage));
else
sb.append(statusMessage);
}
}
if (!sb.isEmpty()) {
Localizer localizer = Forge.getLocalizer();
if (SOptionPane.showOptionDialog(localizer.getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb, localizer.getMessage("lblImportRemainingCards"), SOptionPane.WARNING_ICON, importOrCancel) == 1) {
return;
}
}
final Deck deck = controller.accept(currentDeck.getName()); //must accept in background thread in case a dialog is shown
if (deck == null) {
return;
}
FThreads.invokeInEdtLater(() -> {
hide();
if (callback != null)
callback.accept(deck);
});
}
}

View File

@@ -2838,6 +2838,7 @@ lblDecklistTitle=Deckliste
lblSummaryStats=Gesamt-Statistik
lblDeckSection=Bereich
lblNewDeckCheckbox=Erzeuge ein neues Deck
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Importiere Karten
lblCreateNewCmd=Neues Deck
lblErrNotAllowedCard=Set ist nicht erlaubt in {0}
@@ -2845,6 +2846,7 @@ lblWarnLimitedCard={0} in {1}
lblErrCardEditionDate=Set verträgt sich nicht mit der Erscheinungsdatum-Option
lblErrUnsupportedCard=Ist nicht erlaubt in {0}
lblWarnUnknownCardMsg=Unbekannte oder in Forge nicht unterstützte Karte
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=Aktueller {0}-Bereich enthält {1} mögliche Commander-Karten: {2}
lblWarnCommandersInSideExtra=Bitte prüfen und, falls nötig, min. eine Karte in den Commander-Bereich verschieben.
lblWarnDeckSectionNotAllowedInEditor=In {1} ist der {0}-Bereich nicht erlaubt.
@@ -2936,6 +2938,7 @@ lblCardImportWarning=\nWarnung: Das Deck {0} wird umbenannt in {1}.
lblConfirmCreateNewDeck=Du bist dabei das neue Deck {0} zu erzeugen. {1}\n\nWillst du fortfahren?\n\n Hinweis: \
Bitte denk daran den "Save"-Knopf im Deck-Editor zu klicken, um das neue Deck dem Deck-Katalog hinzuzufügen!
lblNewDeckWarning=\nWarnung: Alle ungesicherten Änderungen am aktuellen Deck {0} werden verlorengehen.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Importiere Karten in aktuelles Deck
lblNewDeckDialogTitle=Erzeuge neues Deck
#FNetOverlay.java

View File

@@ -2898,6 +2898,7 @@ lblDecklistTitle=Decklist
lblSummaryStats=Summary Statistics
lblDeckSection=Section
lblNewDeckCheckbox=Create a New Deck
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Import Cards
lblCreateNewCmd=New Deck
lblErrNotAllowedCard=Set not allowed in {0}
@@ -2905,6 +2906,7 @@ lblWarnLimitedCard={0} in {1}
lblErrCardEditionDate=Set not compliant with Release Date option
lblErrUnsupportedCard=Not allowed in {0}
lblWarnUnknownCardMsg=Unknown Card or Unsupported in Forge
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=Current {0} Section contains {1} potential Commander Cards: {2}
lblWarnCommandersInSideExtra=Please check and move one to the Commander Section, in case.
lblWarnDeckSectionNotAllowedInEditor={0} Section is not allowed in {1}
@@ -3001,6 +3003,7 @@ lblCardImportWarning=\nWarning: The deck {0} will be renamed as {1}.
lblConfirmCreateNewDeck=You are about to create a new deck {0}. {1}\n\nWould you like to proceed?\n\n Note: \
Please remember to click on the "Save" button in the Deck Editor to add the new deck to the Catalog!
lblNewDeckWarning=\nWarning: Any unsaved changes to the current deck {0} will be lost.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Import cards in the Current Deck
lblNewDeckDialogTitle=Create a New Deck
#FNetOverlay.java

View File

@@ -2847,6 +2847,7 @@ lblDecklistTitle=Decklist
lblSummaryStats=Summary Statistics
lblDeckSection=Section
lblNewDeckCheckbox=Create a New Deck
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Import Cards
lblCreateNewCmd=New Deck
lblErrNotAllowedCard=Set not allowed in {0}
@@ -2854,6 +2855,7 @@ lblWarnLimitedCard={0} in {1}
lblErrCardEditionDate=Set not compliant with Release Date option
lblErrUnsupportedCard=Not allowed in {0}
lblWarnUnknownCardMsg=Unknown Card or Unsupported in Forge
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=Current {0} Section contains {1} potential Commander Cards: {2}
lblWarnCommandersInSideExtra=Please check and move one to the Commander Section, in case.
lblWarnDeckSectionNotAllowedInEditor={0} Section is not allowed in {1}
@@ -2950,6 +2952,7 @@ lblCardImportWarning=\nWarning: The deck {0} will be renamed as {1}.
lblConfirmCreateNewDeck=You are about to create a new deck {0}. {1}\n\nWould you like to proceed?\n\n Note: \
Please remember to click on the "Save" button in the Deck Editor to add the new deck to the Catalog!
lblNewDeckWarning=\nWarning: Any unsaved changes to the current deck {0} will be lost.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Import cards in the Current Deck
lblNewDeckDialogTitle=Create a New Deck
#FNetOverlay.java

View File

@@ -2840,6 +2840,7 @@ lblDecklistTitle=Liste de deck
lblSummaryStats=Statistiques récapitulatives
lblDeckSection=Section
lblNewDeckCheckbox=Créer un nouveau deck
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Importer des cartes
lblCreateNewCmd=Nouveau Deck
lblErrNotAllowedCard=Définir non autorisé dans {0}
@@ -2847,6 +2848,7 @@ lblWarnLimitedCard={0} dans {1}
lblErrCardEditionDate=Set non conforme avec l'option de date de sortie
lblErrUnsupportedCard=Non autorisé dans {0}
lblWarnUnknownCardMsg=Carte inconnue ou non prise en charge dans Forge
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=La section {0} actuelle contient {1} cartes de commandant potentielles : {2}
lblWarnCommandersInSideExtra=Veuillez vérifier et en déplacer un vers la section Commandant, au cas où.
lblWarnDeckSectionNotAllowedInEditor={0} La section n'est pas autorisée dans {1}
@@ -2944,6 +2946,7 @@ lblCardImportWarning=\nAttention : Le deck {0} sera renommé en {1}.
lblConfirmCreateNewDeck=Vous êtes sur le point de créer un nouveau deck {0}. {1}\n\nVoulez-vous continuer ?\n\n Remarque : \
N'oubliez pas de cliquer sur le bouton "Enregistrer" dans l'éditeur de deck pour ajouter le nouveau deck au catalogue !
lblNewDeckWarning=\nAttention : Toute modification non enregistrée dans le deck actuel {0} sera perdue.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Importer des cartes dans le Deck actuel
lblNewDeckDialogTitle=Créer un nouveau deck
#FNetOverlay.java

View File

@@ -2836,6 +2836,7 @@ lblDecklistTitle=Lista delle Carte da Importare
lblSummaryStats=Statistiche Generali
lblDeckSection=Sezione
lblNewDeckCheckbox=Crea un nuovo mazzo
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Importa le carte
lblCreateNewCmd=Nuovo mazzo
lblErrNotAllowedCard=Edizione non permessa in {0}
@@ -2843,6 +2844,7 @@ lblWarnLimitedCard={0} in {1}
lblErrCardEditionDate=Edizione non valida secondo l'opzione sulla data di pubblicazione selezionata
lblErrUnsupportedCard=Non Permesso in {0}
lblWarnUnknownCardMsg=Carta Sconosciuta, o non supportata in Forge
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=La Sezione {0} contiene {1} potenziali carte Commander: {2}
lblWarnCommandersInSideExtra=Per favore, controlla e nel caso spostane una nella sezione Commander.
lblWarnDeckSectionNotAllowedInEditor={0} Sezione non è permessa in {1}
@@ -2942,6 +2944,7 @@ lblCardImportWarning=\nAttenzione: Il mazzo {0} sarà rinominato come {1}.
lblConfirmCreateNewDeck=Si sta per creare un nuovo mazzo {0}. {1}\n\nSi desidera procedere?\n\n Nota: \
Non dimenticare di premere il tasto "Salva" una volta importate le carte per aggiungere il nuovo mazzo al catalogo!
lblNewDeckWarning=\nAttenzione: Qualsiasi modifica non salvata al mazzo corrente {0} sarà persa.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Importa le carte nel mazzo corrente
lblNewDeckDialogTitle=Crea un nuovo mazzo
#FNetOverlay.java

View File

@@ -2835,6 +2835,7 @@ lblDecklistTitle=Decklist
lblSummaryStats=Summary Statistics
lblDeckSection=Section
lblNewDeckCheckbox=Create a New Deck
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Import Cards
lblCreateNewCmd=New Deck
lblErrNotAllowedCard=Set not allowed in {0}
@@ -2842,6 +2843,7 @@ lblWarnLimitedCard={0} in {1}
lblErrCardEditionDate=Set not compliant with Release Date option
lblErrUnsupportedCard=Not allowed in {0}
lblWarnUnknownCardMsg=Unknown Card or Unsupported in Forge
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=Current {0} Section contains {1} potential Commander Cards: {2}
lblWarnCommandersInSideExtra=Please check and move one to the Commander Section, in case.
lblWarnDeckSectionNotAllowedInEditor={0} Section is not allowed in {1}
@@ -2938,6 +2940,7 @@ lblCardImportWarning=\nWarning: The deck {0} will be renamed as {1}.
lblConfirmCreateNewDeck=You are about to create a new deck {0}. {1}\n\nWould you like to proceed?\n\n Note: \
Please remember to click on the "Save" button in the Deck Editor to add the new deck to the Catalog!
lblNewDeckWarning=\nWarning: Any unsaved changes to the current deck {0} will be lost.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Import cards in the Current Deck
lblNewDeckDialogTitle=Create a New Deck
#FNetOverlay.java

View File

@@ -2917,6 +2917,7 @@ lblDecklistTitle=Lista de decks
lblSummaryStats=Estatísticas Resumidas
lblDeckSection=Seção
lblNewDeckCheckbox=Criar um Novo deck
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=Importar Cartas
lblCreateNewCmd=Novo Deck
lblErrNotAllowedCard=Coleção não permitida em {0}
@@ -2924,6 +2925,7 @@ lblWarnLimitedCard={0} em {1}
lblErrCardEditionDate=Coleção não compatível com a opção de Data de Lançamento
lblErrUnsupportedCard=Não permitido em {0}
lblWarnUnknownCardMsg=Carta desconhecida ou não suportada no Forge
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=Seção {0} Atual contém {1} Cartas de Comandante em potencial\: {2}
lblWarnCommandersInSideExtra=Verifique e mova um para a seção do Comandante.
lblWarnDeckSectionNotAllowedInEditor=Seção {0} não é permitida em {1}
@@ -3012,6 +3014,7 @@ Deseja prosseguir?\n\
Nota\: Por favor, lembre-se de clicar no botão "Salvar" no Editor do Deck para adicioná-lo ao Catálogo\!
lblNewDeckWarning=\n\
Aviso\: Qualquer alteração não salva no deck atual {0} será perdida.
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=Importar cartas no Deck Atual
lblNewDeckDialogTitle=Criar um Novo deck
#FNetOverlay.java

View File

@@ -2844,6 +2844,7 @@ lblDecklistTitle=套牌列表
lblSummaryStats=统计摘要
lblDeckSection=部分
lblNewDeckCheckbox=创建一个新套牌
lblReplaceDeckCheckbox=Replace Current Deck
lblImportCardsCmd=导入牌张
lblCreateNewCmd=新建套牌
lblErrNotAllowedCard=系列{0}不被允许
@@ -2851,6 +2852,7 @@ lblWarnLimitedCard={0}中的{1}
lblErrCardEditionDate=不符合上市日期选项
lblErrUnsupportedCard={0}不被允许
lblWarnUnknownCardMsg=未知的牌张或未被forge支持的牌张
lblWarnNotInInventory=Card not found in inventory
lblWarnTooManyCommanders=现在{0}部分包含{1}张潜在的指挥官牌张: {2}
lblWarnCommandersInSideExtra=如果确实是指挥官,请进行进行检查并将其中的一张移动到指挥官区。
lblWarnDeckSectionNotAllowedInEditor={0}部分中的{1}不被允许
@@ -2928,6 +2930,7 @@ lblCardImportWarning=\n警告套牌{0}将被重命名为{1}。
lblConfirmCreateNewDeck=你即将创建一个新套牌{0}。{1}\n\n你想要继续吗\n\n 注意:请记得点击套牌编辑器中的\"保存按钮\"以\
将新建的套牌保存到目录中!
lblNewDeckWarning=\n警告对于当前套牌{0}的任何未保存更改丢将被丢弃。
lblConfirmReplaceDeck=This will replace the contents of the current deck ({0}) with the imported cards.\n\nWould you like to proceed?
lblImportCardsDialogTitle=将牌张导入到当前套牌
lblNewDeckDialogTitle=创建一个新套牌
#FNetOverlay.java

View File

@@ -13,15 +13,24 @@ import forge.gui.util.SOptionPane;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.util.ItemPool;
import forge.util.Localizer;
import forge.util.StreamUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.text.DateFormatSymbols;
import java.util.*;
import java.util.function.Function;
public class DeckImportController {
private boolean createNewDeck;
public enum ImportBehavior {
MERGE,
CREATE_NEW,
REPLACE_CURRENT
}
private ImportBehavior importBehavior;
// Date filter
private final ICheckBox dateTimeCheck;
private final IComboBox<String> monthDropdown;
@@ -30,7 +39,7 @@ public class DeckImportController {
private CardDb.CardArtPreference artPreference;
private boolean smartCardArt;
// Block Preference Filter
private boolean inlcludeBnRInDeck = false;
private boolean includeBnRInDeck = false;
private final List<Token> tokens = new ArrayList<>();
private final Map<PaperCard, Token> cardsInTokens = new HashMap<>();
@@ -38,7 +47,14 @@ public class DeckImportController {
private Deck currentDeckInEditor = null;
private DeckFormat currentDeckFormat;
private GameFormat currentGameFormat;
private GameType currentGameType;
private final List<DeckSection> allowedSections = new ArrayList<>();
private ItemPool<PaperCard> playerInventory;
/**
* If a free card is missing from a player's inventory (e.g. a basic land), it gets run through this function, which
* can handle creation of a usable print.
*/
private Function<PaperCard, PaperCard> freePrintSupplier;
public DeckImportController(ICheckBox dateTimeCheck0,
IComboBox<String> monthDropdown0, IComboBox<Integer> yearDropdown0,
@@ -54,13 +70,14 @@ public class DeckImportController {
*/
this.currentDeckNotEmpty = currentDeckNotEmpty;
// this option will control the "new deck" action controlled by UI widget
createNewDeck = false;
this.importBehavior = ImportBehavior.MERGE;
// Init default parameters
this.artPreference = StaticData.instance().getCardArtPreference(); // default
this.smartCardArt = StaticData.instance().isEnabledCardArtSmartSelection();
this.currentDeckFormat = null;
this.currentGameFormat = null;
this.currentGameType = null;
fillDateDropdowns();
}
@@ -68,13 +85,23 @@ public class DeckImportController {
if (gameType == null){
this.currentGameFormat = null;
this.currentDeckFormat = null;
this.currentGameType = null;
} else {
// get the game format with the same name of current game type (if any)
this.currentDeckFormat = gameType.getDeckFormat();
this.currentGameFormat = FModel.getFormats().get(gameType.name());
this.currentGameType = gameType;
}
}
public void setPlayerInventory(ItemPool<PaperCard> inventory) {
this.playerInventory = inventory;
}
public void setFreePrintConverter(Function<PaperCard, PaperCard> freePrintSupplier) {
this.freePrintSupplier = freePrintSupplier;
}
public void setCurrentDeckInEditor(Deck deckInEditor){
this.currentDeckInEditor = deckInEditor;
}
@@ -105,11 +132,13 @@ public class DeckImportController {
return this.smartCardArt;
}
public void setCreateNewDeck(boolean createNewDeck){
this.createNewDeck = createNewDeck;
public void setImportBehavior(ImportBehavior importBehavior) {
this.importBehavior = importBehavior;
}
public boolean getCreateNewDeck() { return this.createNewDeck; }
public ImportBehavior getImportBehavior() {
return importBehavior;
}
private void fillDateDropdowns() {
DateFormatSymbols dfs = new DateFormatSymbols();
@@ -159,10 +188,10 @@ public class DeckImportController {
}
public void importBannedAndRestrictedCards(boolean includeBannedAndRestricted){
this.inlcludeBnRInDeck = includeBannedAndRestricted;
this.includeBnRInDeck = includeBannedAndRestricted;
}
public boolean importBannedAndRestrictedCards(){ return this.inlcludeBnRInDeck; }
public boolean importBannedAndRestrictedCards(){ return this.includeBnRInDeck; }
public List<Token> parseInput(String input) {
tokens.clear();
@@ -186,7 +215,7 @@ public class DeckImportController {
if (!this.allowedSections.isEmpty())
recognizer.setAllowedDeckSections(this.allowedSections);
// Banned and Restricted Card Policy
if (this.inlcludeBnRInDeck)
if (this.includeBnRInDeck)
recognizer.forceImportBannedAndRestrictedCards();
String[] lines = input.split("\n");
@@ -196,8 +225,13 @@ public class DeckImportController {
if (this.currentGameFormatAllowsCommander()) {
List<Pair<Integer, Token>> commanderTokens = getTokensInSection(DeckSection.Commander);
if (commanderTokens.isEmpty()) // Check commanders in Sideboard only if the commander section is empty
checkAndFixCommanderIn(DeckSection.Sideboard);
if (commanderTokens.isEmpty()) {
// Check commanders in Sideboard only if the commander section is empty
if(!getTokensInSection(DeckSection.Sideboard).isEmpty())
checkAndFixCommanderIn(DeckSection.Sideboard);
else
checkAndFixCommanderIn(DeckSection.Main);
}
checkAndFixCommanderIn(DeckSection.Commander);
}
@@ -309,7 +343,7 @@ public class DeckImportController {
}
public boolean currentGameFormatAllowsCommander(){
return this.allowedSections.contains(DeckSection.Commander);
return this.allowedSections.contains(DeckSection.Commander) || this.currentGameType == GameType.PlanarConquest;
}
public List<Token> optimiseCardArtInTokens(){
@@ -332,13 +366,8 @@ public class DeckImportController {
else
refTokenMap = tokensPerSectionWithSet;
List<Token> tokensInSection = refTokenMap.getOrDefault(tokenSection, null);
if (tokensInSection == null) {
tokensInSection = new ArrayList<>();
tokensInSection.add(token);
refTokenMap.put(tokenSection, tokensInSection);
} else
tokensInSection.add(token);
List<Token> tokensInSection = refTokenMap.computeIfAbsent(tokenSection, e -> new ArrayList<>());
tokensInSection.add(token);
}
if (tokensPerSectionWithNoSet.isEmpty())
@@ -356,7 +385,7 @@ public class DeckImportController {
Map<DeckSection, CardPool> referencePoolPerSection = new HashMap<>();
if (this.currentDeckNotEmpty && !this.createNewDeck && this.currentDeckInEditor != null){
if (this.currentDeckNotEmpty && this.importBehavior == ImportBehavior.MERGE && this.currentDeckInEditor != null){
// We will always consider ONLY sections for cards needing art optimisation
for (DeckSection section : tokensPerSectionWithNoSet.keySet()){
CardPool cardsInDeck = this.currentDeckInEditor.get(section);
@@ -436,6 +465,100 @@ public class DeckImportController {
return tokens;
}
public List<Token> constrainTokensToInventory() {
if(this.playerInventory == null)
return tokens;
CardPool availableInventory = new CardPool(this.playerInventory);
//Map of tokens to the things we're gonna replace them with.
Map<Token, List<Token>> tokenReplacers = new LinkedHashMap<>();
//If we're adding to our existing deck, ensure we aren't counting the cards already in it.
if(this.importBehavior == ImportBehavior.MERGE && this.currentDeckInEditor != null)
availableInventory.removeAll(this.currentDeckInEditor.getAllCardsInASinglePool(true, true));
if(this.currentGameType == GameType.PlanarConquest && currentDeckInEditor != null)
availableInventory.removeAllFlat(this.currentDeckInEditor.getCommanders());
//Step 1: For each token, if it's asking for more copies of a print than we can supply, split the difference out
//into a token that's indifferent to the edition. Reduce available inventory accordingly.
for (Token token : this.tokens) {
if (!token.isCardToken())
continue;
PaperCard card = token.getCard();
int requestedAmount = token.getQuantity();
if (card == null)
continue;
if (token.cardRequestHasNoCode()) {
List<Token> list = new ArrayList<>();
tokenReplacers.put(token, list);
continue;
}
int available = availableInventory.count(card);
if (available <= 0) {
List<Token> list = new ArrayList<>();
tokenReplacers.put(token, list);
continue;
}
int numTaken = Math.min(requestedAmount, available);
availableInventory.remove(card, numTaken);
if (available >= requestedAmount)
continue;
List<Token> list = new ArrayList<>();
list.add(Token.LegalCard(card, numTaken, token.getTokenSection(), true));
tokenReplacers.put(token, list);
}
if(tokenReplacers.isEmpty())
return tokens; //We have every card that was requested.
//Step 2: Try to find alternative prints for the ones that do not request an edition.
int capacity = tokens.size();
for(Map.Entry<Token, List<Token>> tokenReplacer : tokenReplacers.entrySet()) {
Token token = tokenReplacer.getKey();
DeckSection tokenSection = token.getTokenSection();
List<Token> replacementList = tokenReplacer.getValue();
PaperCard card = token.getCard();
String cardName = card.getName();
CardPool substitutes = availableInventory.getFilteredPool(c -> c.getName().equals(cardName));
List<Map.Entry<PaperCard, Integer>> sortedSubstitutes = StreamUtil.stream(substitutes).sorted(Comparator.comparingInt(Map.Entry::getValue)).toList();
int neededQuantity = token.getQuantity();
for(Token found : replacementList) {
//If there's an item in the replacement list already it means we've already found some of the needed copies.
neededQuantity -= found.getQuantity();
}
for(int i = 0; i < sortedSubstitutes.size() && neededQuantity > 0; i++) {
Map.Entry<PaperCard, Integer> item = sortedSubstitutes.get(i);
PaperCard replacement = item.getKey();
int toMove = Math.min(neededQuantity, item.getValue());
replacementList.add(Token.LegalCard(replacement, toMove, tokenSection, true));
availableInventory.remove(replacement, toMove);
neededQuantity -= toMove;
capacity++;
}
if(neededQuantity > 0) {
PaperCard freePrint = getInfiniteSupplyPrinting(card);
if(freePrint != null)
replacementList.add(Token.NotInInventoryFree(freePrint, neededQuantity, tokenSection));
else
replacementList.add(Token.NotInInventory(card, neededQuantity, tokenSection));
capacity++;
}
}
//Step 3: Apply the replacement list.
List<Token> newList = new ArrayList<>(capacity);
for(Token t : this.tokens) {
if(tokenReplacers.containsKey(t))
newList.addAll(tokenReplacers.get(t));
else
newList.add(t);
}
this.tokens.clear();
this.tokens.addAll(newList);
return tokens;
}
private PaperCard getInfiniteSupplyPrinting(PaperCard card) {
if(this.freePrintSupplier == null)
return null;
return freePrintSupplier.apply(card);
}
private int countTokens(List<Token> tokensInSection){
if (tokensInSection == null || tokensInSection.isEmpty())
return 0;
@@ -487,27 +610,31 @@ public class DeckImportController {
if (tokens.isEmpty()) { return null; }
String deckName = "";
if (currentDeckName != null && currentDeckName.trim().length() > 0)
if (currentDeckName != null && !currentDeckName.trim().isEmpty())
deckName = String.format("\"%s\"", currentDeckName.trim());
String tokenDeckName = getTokenDeckNameIfAny();
if (tokenDeckName.length() > 0)
if (!tokenDeckName.isEmpty())
tokenDeckName = String.format("\"%s\"", tokenDeckName);
if (createNewDeck){
String extraWarning = currentDeckNotEmpty ? localizer.getMessage("lblNewDeckWarning", deckName) : "";
final String warning = localizer.getMessage("lblConfirmCreateNewDeck", tokenDeckName, extraWarning);
if (!SOptionPane.showConfirmDialog(warning, localizer.getMessage("lblNewDeckDialogTitle"),
localizer.getMessage("lblYes"), localizer.getMessage("lblNo"))) {
return null;
if(this.currentDeckNotEmpty) {
final String warning;
final String title;
if (this.importBehavior == ImportBehavior.CREATE_NEW) {
String extraWarning = localizer.getMessage("lblNewDeckWarning", deckName);
warning = localizer.getMessage("lblConfirmCreateNewDeck", tokenDeckName, extraWarning);
title = localizer.getMessage("lblNewDeckDialogTitle");
} else if (this.importBehavior == ImportBehavior.MERGE){
String extraWarning = (!tokenDeckName.isEmpty() && !tokenDeckName.equals(deckName)) ?
localizer.getMessage("lblCardImportWarning", deckName, tokenDeckName) : "";
warning = localizer.getMessage("lblConfirmCardImport", deckName, extraWarning);
title = localizer.getMessage("lblImportCardsDialogTitle");
}
}
else if (this.currentDeckNotEmpty){
String extraWarning = (tokenDeckName.length() > 0 && !tokenDeckName.equals(deckName)) ?
localizer.getMessage("lblCardImportWarning", deckName, tokenDeckName) : "";
final String warning = localizer.getMessage("lblConfirmCardImport", deckName, extraWarning);
if (!SOptionPane.showConfirmDialog(warning,
localizer.getMessage("lblImportCardsDialogTitle"),
else {
warning = localizer.getMessage("lblConfirmReplaceDeck", deckName);
title = localizer.getMessage("lblNewDeckDialogTitle");
}
if (!SOptionPane.showConfirmDialog(warning, title,
localizer.getMessage("lblYes"), localizer.getMessage("lblNo")))
return null;
}
@@ -516,7 +643,7 @@ public class DeckImportController {
final TokenType type = t.getType();
// only Deck Name, legal card and limited card tokens will be analysed!
if (!t.isTokenForDeck() ||
(type == TokenType.LIMITED_CARD && !this.inlcludeBnRInDeck))
(type == TokenType.LIMITED_CARD && !this.includeBnRInDeck))
continue; // SKIP token
if (type == TokenType.DECK_NAME) {
@@ -547,4 +674,66 @@ public class DeckImportController {
}
return ""; // no deck name
}
public String getTokenMessage(DeckRecognizer.Token token) {
return switch (token.getType()) {
case LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET, CARD_FROM_INVALID_SET,
CARD_NOT_IN_INVENTORY, FREE_CARD_NOT_IN_INVENTORY ->
String.format("%s x %s %s", token.getQuantity(), token.getText(), getTokenFoilLabel(token));
// Card Warning Msgs
case UNKNOWN_CARD, UNSUPPORTED_CARD ->
token.getQuantity() > 0 ? String.format("%s x %s", token.getQuantity(), token.getText())
: token.getText();
case UNSUPPORTED_DECK_SECTION ->
String.format("%s: %s", Localizer.getInstance().getMessage("lblWarningMsgPrefix"),
Localizer.getInstance()
.getMessage("lblWarnDeckSectionNotAllowedInEditor", token.getText(),
this.currentGameType.name()));
// Special Case of Card moved into another section (e.g. Commander from Sideboard)
case WARNING_MESSAGE -> String.format("%s: %s", Localizer.getInstance()
.getMessage("lblWarningMsgPrefix"), token.getText());
// Placeholders
case DECK_SECTION_NAME -> String.format("%s: %s", Localizer.getInstance().getMessage("lblDeckSection"),
token.getText());
case CARD_RARITY -> String.format("%s: %s", Localizer.getInstance().getMessage("lblRarity"),
token.getText());
case CARD_TYPE, CARD_CMC, MANA_COLOUR, COMMENT, UNKNOWN_TEXT -> token.getText();
case DECK_NAME -> String.format("%s: %s", Localizer.getInstance().getMessage("lblDeckName"),
token.getText());
};
}
public String getTokenStatusMessage(DeckRecognizer.Token token) {
if (token == null)
return "";
final Localizer localizer = Localizer.getInstance();
return switch (token.getType()) {
case LIMITED_CARD -> String.format("%s: %s", localizer.getMessage("lblWarningMsgPrefix"),
localizer.getMessage("lblWarnLimitedCard",
StringUtils.capitalize(token.getLimitedCardType().name()), getGameFormatLabel()));
case CARD_FROM_NOT_ALLOWED_SET ->
localizer.getMessage("lblErrNotAllowedCard", getGameFormatLabel());
case CARD_FROM_INVALID_SET -> localizer.getMessage("lblErrCardEditionDate");
case UNSUPPORTED_CARD -> localizer.getMessage("lblErrUnsupportedCard", this.currentGameType);
case UNKNOWN_CARD -> String.format("%s: %s", localizer.getMessage("lblWarningMsgPrefix"),
localizer.getMessage("lblWarnUnknownCardMsg"));
case CARD_NOT_IN_INVENTORY -> localizer.getMessage("lblWarnNotInInventory");
default -> "";
};
}
private String getTokenFoilLabel(DeckRecognizer.Token token) {
if (!token.isCardToken())
return "";
final String foilMarker = "- (Foil)";
return token.getCard().isFoil() ? foilMarker : "";
}
private String getGameFormatLabel() {
return String.format("\"%s\"", this.getCurrentGameFormatName());
}
}

View File

@@ -774,7 +774,7 @@ public class DeckProxy implements InventoryItem {
for (PaperCard c : deck.getAllCardsInASinglePool().toFlatList()) {
CardEdition edition = FModel.getMagicDb().getEditions().get(c.getEdition());
if (edition == null)
if (edition == null || !edition.hasBasicLands())
continue;
availableEditions.add(edition);
}

View File

@@ -18,15 +18,11 @@
package forge.gamemodes.planarconquest;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import forge.card.CardDb;
import forge.gamemodes.planarconquest.ConquestPreferences.CQPref;
@@ -199,14 +195,11 @@ public final class ConquestData {
}
public int getAccessiblePlaneCount() {
// TODO: Java 8 stream implementation of filtering
int i = 0;
for (ConquestPlane plane : FModel.getPlanes()) {
if (!plane.isUnreachable()) {
i++;
}
}
return i;
return (int) FModel.getPlanes().stream().filter(Predicate.not(ConquestPlane::isUnreachable)).count();
}
public Set<ConquestPlane> getUnlockedPlanes() {
return planeDataMap.values().stream().map(ConquestPlaneData::getLocation).map(ConquestLocation::getPlane).collect(Collectors.toSet());
}
public void unlockPlane(ConquestPlane plane) {
@@ -302,7 +295,7 @@ public final class ConquestData {
}
}
if (commandersUsingCard.length() > 0) {
if (!commandersUsingCard.isEmpty()) {
SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblCommandersCardCannotBeExiledByCard", CardTranslation.getTranslatedName(card.getName()), commandersUsingCard), title, SOptionPane.INFORMATION_ICON);
return false;
}

View File

@@ -50,6 +50,7 @@ public class ConquestPlane {
private FCollection<PaperCard> commanders;
private ConquestAwardPool awardPool;
private ConquestEvent[] events;
private final Set<CardEdition> editions = new HashSet<>();
private ConquestPlane(String name0, String description0, int regionSize0, boolean unreachable0) {
name = name0;
@@ -153,6 +154,10 @@ public class ConquestPlane {
return planeCards;
}
public Set<CardEdition> getEditions() {
return editions;
}
private void ensureRegionsLoaded() {
if (regions != null) { return; }
@@ -193,6 +198,8 @@ public class ConquestPlane {
if (edition == null)
continue;
editions.add(edition);
for (EditionEntry card : edition.getObtainableCards()) {
if (bannedCardSet == null || !bannedCardSet.contains(card.name())) {
addCard(commonCards.getCard(card.name(), setCode));

View File

@@ -1,22 +1,16 @@
package forge.gamemodes.planarconquest;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.card.CardRarity;
import forge.card.CardRules;
import forge.card.CardType;
import forge.card.*;
import forge.card.CardType.CoreType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.deck.CardPool;
import forge.deck.Deck;
@@ -143,6 +137,25 @@ public class ConquestUtil {
return pool;
}
public static List<CardEdition> getBasicLandSets(Deck currentDeck) {
ConquestData model = FModel.getConquest().getModel();
List<ConquestPlane> planes = new ArrayList<>(model.getUnlockedPlanes());
ConquestPlane currentPlane = model.getCurrentPlane();
//Move the current plane to the front.
if(currentPlane != null && planes.contains(currentPlane)) {
planes.remove(currentPlane);
planes.add(0, currentPlane);
}
//Move editions of cards already in the deck to the front.
Map<CardEdition, Integer> editionStats = currentDeck.getAllCardsInASinglePool().getCardEditionStatistics(true);
List<CardEdition> out = planes.stream()
.<CardEdition>mapMulti((p, c) -> p.getEditions().forEach(c))
.filter(CardEdition::hasBasicLands)
.sorted(Comparator.comparing(e -> editionStats.getOrDefault(e, 0)))
.collect(Collectors.toList());
return out;
}
public static ConquestPlane getPlaneByName(String planeName) {
for (ConquestPlane plane : FModel.getPlanes()) {
if (plane.getName().equals(planeName)) {
@@ -189,19 +202,16 @@ public class ConquestUtil {
public static int getShardValue(CardRarity rarity, CQPref baseValuePref) {
ConquestPreferences prefs = FModel.getConquestPreferences();
int baseValue = prefs.getPrefInt(baseValuePref);
switch (rarity) {
case Common:
return baseValue;
case Uncommon:
return Math.round((float)baseValue * (float)prefs.getPrefInt(CQPref.AETHER_UNCOMMON_MULTIPLIER));
case Rare:
case Special:
return Math.round((float)baseValue * (float)prefs.getPrefInt(CQPref.AETHER_RARE_MULTIPLIER));
case MythicRare:
return Math.round((float)baseValue * (float)prefs.getPrefInt(CQPref.AETHER_MYTHIC_MULTIPLIER));
default:
return 0;
}
return switch (rarity) {
case Common -> baseValue;
case Uncommon ->
Math.round((float) baseValue * (float) prefs.getPrefInt(CQPref.AETHER_UNCOMMON_MULTIPLIER));
case Rare, Special ->
Math.round((float) baseValue * (float) prefs.getPrefInt(CQPref.AETHER_RARE_MULTIPLIER));
case MythicRare ->
Math.round((float) baseValue * (float) prefs.getPrefInt(CQPref.AETHER_MYTHIC_MULTIPLIER));
default -> 0;
};
}
public enum AEtherFilter implements IHasSkinProp {

View File

@@ -19,6 +19,7 @@ package forge.gamemodes.quest;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import com.google.common.eventbus.Subscribe;
@@ -620,17 +621,21 @@ public class QuestController {
}
public CardEdition getDefaultLandSet() {
List<String> availableEditionCodes = questFormat != null ? questFormat.getAllowedSetCodes() : Lists.newArrayList(FModel.getMagicDb().getEditions().getItemNames());
List<CardEdition> availableEditions = new ArrayList<>();
for (String s : availableEditionCodes) {
availableEditions.add(FModel.getMagicDb().getEditions().get(s));
}
List<CardEdition> availableEditions = getAvailableLandSets();
CardEdition randomLandSet = CardEdition.Predicates.getRandomSetWithAllBasicLands(availableEditions);
return randomLandSet == null ? FModel.getMagicDb().getEditions().get("ZEN") : randomLandSet;
}
public List<CardEdition> getAvailableLandSets() {
List<String> availableEditionCodes = questFormat != null ? questFormat.getAllowedSetCodes() : Lists.newArrayList(FModel.getMagicDb().getEditions().getItemNames());
CardEdition.Collection editions = FModel.getMagicDb().getEditions();
return availableEditionCodes.stream()
.map(editions::get)
.filter(CardEdition::hasBasicLands)
.collect(Collectors.toList());
}
public String getCurrentDeck() {
return model.currentDeck;
}