Merge branch 'master' into addthefivedoctors

This commit is contained in:
xenohedron 2025-05-29 01:03:39 -04:00 committed by GitHub
commit 2aeb6420c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
736 changed files with 17075 additions and 6454 deletions

View file

@ -114,12 +114,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
int cardWidth = getCardWidth(); int cardWidth = getCardWidth();
int cardHeight = getCardHeight(); int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth); int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING); int dx = x % (cardWidth + getGridPadding());
int col = x / (cardWidth + GRID_PADDING); int col = x / (cardWidth + getGridPadding());
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size(); int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
int countLabelHeight = getCountLabelHeight(); int countLabelHeight = getCountLabelHeight();
if (dx < GRID_PADDING && col < gridWidth) { if (dx < getGridPadding() && col < gridWidth) {
// Which row to add to? // Which row to add to?
int curY = countLabelHeight; int curY = countLabelHeight;
int rowIndex = 0; int rowIndex = 0;
@ -142,7 +142,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Insert between two columns // Insert between two columns
insertArrow.setIcon(INSERT_COL_ICON); insertArrow.setIcon(INSERT_COL_ICON);
insertArrow.setSize(64, 64); insertArrow.setSize(64, 64);
insertArrow.setLocation((cardWidth + GRID_PADDING) * col + GRID_PADDING / 2 - 32, curY); insertArrow.setLocation((cardWidth + getGridPadding()) * col + getGridPadding() / 2 - 32, curY);
} else { } else {
// Clamp to a new col one after the current last one // Clamp to a new col one after the current last one
col = Math.min(col, gridWidth); col = Math.min(col, gridWidth);
@ -184,7 +184,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Position arrow // Position arrow
insertArrow.setIcon(INSERT_ROW_ICON); insertArrow.setIcon(INSERT_ROW_ICON);
insertArrow.setSize(64, 32); insertArrow.setSize(64, 32);
insertArrow.setLocation((cardWidth + GRID_PADDING) * col + GRID_PADDING + cardWidth / 2 - 32, curY + stackInsertIndex * cardTopHeight - 32); insertArrow.setLocation((cardWidth + getGridPadding()) * col + getGridPadding() + cardWidth / 2 - 32, curY + stackInsertIndex * cardTopHeight - 32);
} }
} }
@ -225,12 +225,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
int cardWidth = getCardWidth(); int cardWidth = getCardWidth();
int cardHeight = getCardHeight(); int cardHeight = getCardHeight();
int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth); int cardTopHeight = CardRenderer.getCardTopHeight(cardWidth);
int dx = x % (cardWidth + GRID_PADDING); int dx = x % (cardWidth + getGridPadding());
int col = x / (cardWidth + GRID_PADDING); int col = x / (cardWidth + getGridPadding());
int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size(); int gridWidth = cardGrid.isEmpty() ? 0 : cardGrid.get(0).size();
int countLabelHeight = getCountLabelHeight(); int countLabelHeight = getCountLabelHeight();
if (dx < GRID_PADDING && col < gridWidth) { if (dx < getGridPadding() && col < gridWidth) {
// Which row to add to? // Which row to add to?
int curY = countLabelHeight; int curY = countLabelHeight;
int rowIndex = 0; int rowIndex = 0;
@ -334,7 +334,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Add new cards to grid // Add new cards to grid
for (CardView card : cards) { for (CardView card : cards) {
card.setSelected(true); card.setSelected(true);
addCardView(card, false); addCardView(card, null);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
} }
layoutGrid(); layoutGrid();
@ -575,7 +575,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// Constants // Constants
private static final int DEFAULT_COUNT_LABEL_HEIGHT = 40; // can contain 1 or 2 lines private static final int DEFAULT_COUNT_LABEL_HEIGHT = 40; // can contain 1 or 2 lines
public static final int GRID_PADDING = 10; public static final int GRID_PADDING = 20;
private static final ImageIcon INSERT_ROW_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_row.png")); private static final ImageIcon INSERT_ROW_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_row.png"));
private static final ImageIcon INSERT_COL_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_col.png")); private static final ImageIcon INSERT_COL_ICON = new ImageIcon(DragCardGrid.class.getClassLoader().getResource("editor_insert_col.png"));
@ -1044,7 +1044,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
selectBySearchPanelC.fill = GridBagConstraints.VERTICAL; selectBySearchPanelC.fill = GridBagConstraints.VERTICAL;
searchByTextField = new JTextField(); searchByTextField = new JTextField();
searchByTextField.setToolTipText("Searches for card names, types, rarity, casting cost and rules text. NB: Mana symbols are written like {W},{U},{C} etc"); searchByTextField.setToolTipText("Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)");
searchByTextField.addKeyListener(new KeyAdapter() { searchByTextField.addKeyListener(new KeyAdapter() {
@Override @Override
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
@ -1253,10 +1253,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
selectionPanel.setSize(x2 - x1, y2 - y1); selectionPanel.setSize(x2 - x1, y2 - y1);
// First and last cols // First and last cols
int col1 = x1 / (cardWidth + GRID_PADDING); int col1 = x1 / (cardWidth + getGridPadding());
int col2 = x2 / (cardWidth + GRID_PADDING); int col2 = x2 / (cardWidth + getGridPadding());
int offsetIntoCol2 = x2 % (cardWidth + GRID_PADDING); int offsetIntoCol2 = x2 % (cardWidth + getGridPadding());
if (offsetIntoCol2 < GRID_PADDING) { if (offsetIntoCol2 < getGridPadding()) {
--col2; --col2;
} }
@ -1322,7 +1322,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
// re-insert // re-insert
for (CardView card : allCards) { for (CardView card : allCards) {
sortIntoGrid(card); sortIntoGrid(card, null);
} }
trimGrid(); trimGrid();
@ -1717,7 +1717,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (acard.getName().equals(card.getName())) { if (acard.getName().equals(card.getName())) {
CardView pimpedCard = new CardView(acard); CardView pimpedCard = new CardView(acard);
addCardView(pimpedCard, false); addCardView(pimpedCard, null);
eventSource.fireEvent(pimpedCard, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(pimpedCard, ClientEventType.DECK_ADD_SPECIFIC_CARD);
pimpedCards.put(pimpedCard, 1); pimpedCards.put(pimpedCard, 1);
didModify = true; didModify = true;
@ -1729,7 +1729,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
if (didModify) { if (didModify) {
for (CardView c : pimpedCards.keySet()) { for (CardView c : pimpedCards.keySet()) {
sortIntoGrid(c); sortIntoGrid(c, null);
} }
trimGrid(); trimGrid();
@ -1762,7 +1762,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
CardView oldestCardView = new CardView(oldestCardInfo.createMockCard()); CardView oldestCardView = new CardView(oldestCardInfo.createMockCard());
this.removeCardView(card); this.removeCardView(card);
eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD); eventSource.fireEvent(card, ClientEventType.DECK_REMOVE_SPECIFIC_CARD);
this.addCardView(oldestCardView, false); this.addCardView(oldestCardView, null);
eventSource.fireEvent(oldestCardView, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(oldestCardView, ClientEventType.DECK_ADD_SPECIFIC_CARD);
newStack.add(oldestCardView); newStack.add(oldestCardView);
} else { } else {
@ -1814,10 +1814,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView newCard : cardsView.values()) { for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) { if (!cardViews.containsKey(newCard.getId())) {
// Is a new card // Is a new card
addCardView(newCard, false); addCardView(newCard, null);
// Put it into the appropirate place in the grid given the current sort // Put it into the appropirate place in the grid given the current sort
sortIntoGrid(newCard); sortIntoGrid(newCard, null);
// Mark // Mark
didModify = true; didModify = true;
@ -1837,7 +1837,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (CardView newCard : cardsView.values()) { for (CardView newCard : cardsView.values()) {
if (!cardViews.containsKey(newCard.getId())) { if (!cardViews.containsKey(newCard.getId())) {
// Add the new card // Add the new card
addCardView(newCard, false); addCardView(newCard, null);
// Add the new card to tracking // Add the new card to tracking
Map<String, List<CardView>> forSetCode; Map<String, List<CardView>> forSetCode;
@ -1889,7 +1889,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (List<CardView> orphans : tracked.values()) { for (List<CardView> orphans : tracked.values()) {
for (CardView orphan : orphans) { for (CardView orphan : orphans) {
logger.info("Orphan when setting with layout: "); logger.info("Orphan when setting with layout: ");
sortIntoGrid(orphan); sortIntoGrid(orphan, null);
} }
} }
} }
@ -1984,7 +1984,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
menu.show(e.getComponent(), e.getX(), e.getY()); menu.show(e.getComponent(), e.getX(), e.getY());
} }
public void addCardView(final CardView card, boolean duplicated) { public void addCardView(final CardView card, final CardView duplicatedFromCard) {
allCards.add(card); allCards.add(card);
// Update counts // Update counts
@ -2019,8 +2019,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
cardContent.add(cardPanel); cardContent.add(cardPanel);
cardViews.put(card.getId(), cardPanel); cardViews.put(card.getId(), cardPanel);
if (duplicated) { if (duplicatedFromCard != null) {
sortIntoGrid(card); sortIntoGrid(card, duplicatedFromCard);
eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD); eventSource.fireEvent(card, ClientEventType.DECK_ADD_SPECIFIC_CARD);
// clear grid from empty rows // clear grid from empty rows
@ -2094,7 +2094,21 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
* *
* @param newCard Card to add to the cardGrid array. * @param newCard Card to add to the cardGrid array.
*/ */
private void sortIntoGrid(CardView newCard) { private void sortIntoGrid(CardView newCard, CardView duplicatedFromCard) {
// fast put duplicated card to the same place as original
if (duplicatedFromCard != null) {
for (List<List<CardView>> gridRow : cardGrid) {
for (List<CardView> gridStack : gridRow) {
for (int i = 0; i < gridStack.size(); i++) {
if (gridStack.get(i).equals(duplicatedFromCard)) {
gridStack.add(i, newCard);
return;
}
}
}
}
}
// row 1 must exists // row 1 must exists
if (cardGrid.isEmpty()) { if (cardGrid.isEmpty()) {
cardGrid.add(0, new ArrayList<>()); cardGrid.add(0, new ArrayList<>());
@ -2336,7 +2350,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
} else { } else {
String description = cardSort.getComparator().getCategoryName(stack.get(0)); String description = cardSort.getComparator().getCategoryName(stack.get(0));
DragCardGrid.updateCountLabel(countLabel, stack.size(), description); DragCardGrid.updateCountLabel(countLabel, stack.size(), description);
countLabel.setLocation(GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex, currentY - countLabelHeight); countLabel.setLocation(getGridPadding() + (cardWidth + getGridPadding()) * colIndex, currentY - countLabelHeight);
countLabel.setSize(cardWidth, countLabelHeight); countLabel.setSize(cardWidth, countLabelHeight);
countLabel.setVisible(true); countLabel.setVisible(true);
} }
@ -2348,7 +2362,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
for (int i = 0; i < stack.size(); ++i) { for (int i = 0; i < stack.size(); ++i) {
CardView card = stack.get(i); CardView card = stack.get(i);
MageCard view = cardViews.get(card.getId()); MageCard view = cardViews.get(card.getId());
int x = GRID_PADDING + (cardWidth + GRID_PADDING) * colIndex; int x = getGridPadding() + (cardWidth + getGridPadding()) * colIndex;
int y = currentY + i * cardTopHeight; int y = currentY + i * cardTopHeight;
view.setCardBounds(x, y, cardWidth, cardHeight); view.setCardBounds(x, y, cardWidth, cardHeight);
cardContent.setLayer(view, layerIndex++); cardContent.setLayer(view, layerIndex++);
@ -2356,14 +2370,18 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg
} }
// Update the max stack size for this row and the max width // Update the max stack size for this row and the max width
maxWidth = Math.max(maxWidth, GRID_PADDING + (GRID_PADDING + cardWidth) * gridRow.size()); maxWidth = Math.max(maxWidth, getGridPadding() + (getGridPadding() + cardWidth) * gridRow.size());
maxStackSize.set(rowIndex, rowMaxStackSize); maxStackSize.set(rowIndex, rowMaxStackSize);
currentY += (cardTopHeight * (rowMaxStackSize - 1) + cardHeight) + countLabelHeight; currentY += (cardTopHeight * (rowMaxStackSize - 1) + cardHeight) + countLabelHeight;
} }
// Resize card container // Resize card container
cardContent.setPreferredSize(new Dimension(maxWidth, currentY - countLabelHeight + GRID_PADDING)); cardContent.setPreferredSize(new Dimension(maxWidth, currentY - countLabelHeight + getGridPadding()));
//cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + GRID_PADDING); //cardContent.setSize(maxWidth, currentY - COUNT_LABEL_HEIGHT + getGridPadding());
}
private int getGridPadding() {
return Math.max(GRID_PADDING, Math.round(cardSizeMod * GRID_PADDING * GUISizeHelper.dialogGuiScale));
} }
public static int getCountLabelHeight() { public static int getCountLabelHeight() {

View file

@ -0,0 +1,343 @@
package mage.client.components;
import mage.MageObject;
import mage.cards.Card;
import mage.cards.decks.Deck;
import mage.client.util.GUISizeHelper;
import java.awt.*;
import java.util.List;
import java.util.*;
import java.util.stream.Stream;
/**
* Inject bracket level inside validation panel
* See more details at <a href="https://mtg.wiki/page/Commander_Brackets">wiki</a>
* <p>
* Support:
* - [x] game changers
* - [ ] infinite combos
* - [x] mass land destruction
* - [x] extra turns
* - [x] tutors
*
* @author JayDi85
*/
public class BracketLegalityLabel extends LegalityLabel {
private static final String GROUP_GAME_CHANGES = "Game Changers";
private static final String GROUP_INFINITE_COMBOS = "Infinite Combos (unsupported)";
private static final String GROUP_MASS_LAND_DESTRUCTION = "Mass Land Destruction";
private static final String GROUP_EXTRA_TURN = "Extra Turns";
private static final String GROUP_TUTORS = "Tutors";
private final BracketLevel level;
private final List<String> foundGameChangers = new ArrayList<>();
private final List<String> foundInfiniteCombos = new ArrayList<>();
private final List<String> foundMassLandDestruction = new ArrayList<>();
private final List<String> foundExtraTurn = new ArrayList<>();
private final List<String> foundTutors = new ArrayList<>();
private final List<String> badCards = new ArrayList<>();
private final List<String> fullGameChanges = new ArrayList<>();
public enum BracketLevel {
BRACKET_1("Bracket 1"),
BRACKET_2_3("Bracket 2-3"),
BRACKET_4_5("Bracket 4-5");
private final String name;
BracketLevel(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
public BracketLegalityLabel(BracketLevel level) {
super(level.toString(), null);
this.level = level;
setPreferredSize(DIM_PREFERRED);
}
@Override
public List<String> selectCards() {
return new ArrayList<>(this.badCards);
}
private void validateBracketLevel() {
this.badCards.clear();
switch (this.level) {
case BRACKET_1:
// No cards from the Game Changer list.
// No intentional two-card infinite combos.
// No mass land destruction.
// No extra turn cards.
// Tutors should be sparse.
this.badCards.addAll(this.foundGameChangers);
this.badCards.addAll(this.foundInfiniteCombos);
this.badCards.addAll(this.foundMassLandDestruction);
this.badCards.addAll(this.foundExtraTurn);
if (this.foundTutors.size() > 3) {
this.badCards.addAll(this.foundTutors);
}
break;
case BRACKET_2_3:
// 2
// No cards from the Game Changer list.
// No intentional two-card infinite combos.
// No mass land destruction.
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
// Tutors should be sparse.
// 3
// Up to three (3) cards from the Game Changer list.
// No intentional early game two-card infinite combos.
// No mass land destruction.
// Extra turn cards should only appear in low quantities and should not be chained in succession or looped.
if (this.foundGameChangers.size() > 3) {
this.badCards.addAll(this.foundGameChangers);
}
this.badCards.addAll(this.foundInfiniteCombos);
this.badCards.addAll(this.foundMassLandDestruction);
if (this.foundExtraTurn.size() > 3) {
this.badCards.addAll(this.foundExtraTurn);
}
// this.badCards.addAll(this.foundTutors); // allow any amount
break;
case BRACKET_4_5:
// allow any cards
break;
default:
throw new IllegalArgumentException("Unsupported level: " + this.level);
}
}
@Override
public void validateDeck(Deck deck) {
collectAll(deck);
validateBracketLevel();
int infoFontSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f);
// show all found cards in any use cases
Color showColor = this.badCards.isEmpty() ? COLOR_LEGAL : COLOR_NOT_LEGAL;
List<String> showInfo = new ArrayList<>();
if (this.badCards.isEmpty()) {
showInfo.add("<p>Deck is <span style='color:green;font-weight:bold;'>GOOD</span> for " + this.level + "</p>");
} else {
showInfo.add("<p>Deck is <span style='color:#BF544A;font-weight:bold;'>BAD</span> for " + this.level + "</p>");
showInfo.add("<p>(click here to select all bad cards)</p>");
}
Map<String, List<String>> groups = new LinkedHashMap<>();
groups.put(GROUP_GAME_CHANGES, this.foundGameChangers);
groups.put(GROUP_INFINITE_COMBOS, this.foundInfiniteCombos);
groups.put(GROUP_MASS_LAND_DESTRUCTION, this.foundMassLandDestruction);
groups.put(GROUP_EXTRA_TURN, this.foundExtraTurn);
groups.put(GROUP_TUTORS, this.foundTutors);
groups.forEach((group, cards) -> {
showInfo.add("<br>");
showInfo.add("<br>");
showInfo.add("<span style='font-weight:bold;'>" + group + ": " + cards.size() + "</span>");
if (!cards.isEmpty()) {
showInfo.add("<ul style=\"font-size: " + infoFontSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">");
cards.forEach(s -> showInfo.add(String.format("<li style=\"margin-bottom: 2px;\">%s</li>", s)));
showInfo.add("</ul>");
}
});
String showText = "<html><body>" + String.join("\n", showInfo) + "</body></html>";
showState(showColor, showText, false);
}
private void collectAll(Deck deck) {
collectGameChangers(deck);
collectInfiniteCombos(deck);
collectMassLandDestruction(deck);
collectExtraTurn(deck);
collectTutors(deck);
}
private void collectGameChangers(Deck deck) {
this.foundGameChangers.clear();
if (fullGameChanges.isEmpty()) {
// https://mtg.wiki/page/Game_Changers
// TODO: share list with AbstractCommander and edh power level
fullGameChanges.addAll(Arrays.asList(
"Ad Nauseam",
"Ancient Tomb",
"Aura Shards",
"Bolas's Citadel",
"Braids, Cabal Minion",
"Demonic Tutor",
"Drannith Magistrate",
"Chrome Mox",
"Coalition Victory",
"Consecrated Sphinx",
"Crop Rotation",
"Cyclonic Rift",
"Deflecting Swat",
"Enlightened Tutor",
"Expropriate",
"Field of the Dead",
"Fierce Guardianship",
"Food Chain",
"Force of Will",
"Gaea's Cradle",
"Gamble",
"Gifts Ungiven",
"Glacial Chasm",
"Grand Arbiter Augustin IV",
"Grim Monolith",
"Humility",
"Imperial Seal",
"Intuition",
"Jeska's Will",
"Jin-Gitaxias, Core Augur",
"Kinnan, Bonder Prodigy",
"Lion's Eye Diamond",
"Mana Vault",
"Mishra's Workshop",
"Mox Diamond",
"Mystical Tutor",
"Narset, Parter of Veils",
"Natural Order",
"Necropotence",
"Notion Thief",
"Rhystic Study",
"Opposition Agent",
"Orcish Bowmasters",
"Panoptic Mirror",
"Seedborn Muse",
"Serra's Sanctum",
"Smothering Tithe",
"Survival of the Fittest",
"Sway of the Stars",
"Teferi's Protection",
"Tergrid, God of Fright",
"Thassa's Oracle",
"The One Ring",
"The Tabernacle at Pendrell Vale",
"Underworld Breach",
"Urza, Lord High Artificer",
"Vampiric Tutor",
"Vorinclex, Voice of Hunger",
"Yuriko, the Tiger's Shadow",
"Winota, Joiner of Forces",
"Worldly Tutor"
));
}
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.map(MageObject::getName)
.filter(fullGameChanges::contains)
.sorted()
.forEach(this.foundGameChangers::add);
}
private void collectInfiniteCombos(Deck deck) {
// TODO: implement
this.foundInfiniteCombos.clear();
}
private void collectMassLandDestruction(Deck deck) {
// https://mtg.wiki/page/Land_destruction
// https://draftsim.com/mtg-mass-land-destruction/
this.foundMassLandDestruction.clear();
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.filter(card -> card.getRules().stream()
.map(s -> s.toLowerCase(Locale.ENGLISH))
.anyMatch(s -> (s.contains("destroy") || s.contains("sacrifice"))
&& (s.contains("all") || s.contains("x target") || s.contains("{x} target"))
&& isTextContainsLandName(s)
)
)
.map(Card::getName)
.sorted()
.forEach(this.foundMassLandDestruction::add);
}
private void collectExtraTurn(Deck deck) {
this.foundExtraTurn.clear();
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.filter(card -> card.getRules().stream()
.map(s -> s.toLowerCase(Locale.ENGLISH))
.anyMatch(s -> s.contains("extra turn"))
)
.map(Card::getName)
.sorted()
.forEach(this.foundExtraTurn::add);
}
private void collectTutors(Deck deck) {
// edh power level uses search for land and non-land card, but bracket need only non-land cards searching
this.foundTutors.clear();
Stream.concat(deck.getCards().stream(), deck.getSideboard().stream())
.filter(card -> card.getRules().stream()
.map(s -> s.toLowerCase(Locale.ENGLISH))
.anyMatch(s -> s.contains("search your library") && !isTextContainsLandCard(s))
)
.map(Card::getName)
.sorted()
.forEach(this.foundTutors::add);
}
private boolean isTextContainsLandCard(String lowerText) {
// TODO: share code with AbstractCommander and edh power level
// TODO: add tests
return lowerText.contains("basic ")
|| lowerText.contains("plains card")
|| lowerText.contains("island card")
|| lowerText.contains("swamp card")
|| lowerText.contains("mountain card")
|| lowerText.contains("forest card");
}
private boolean isTextContainsLandName(String lowerText) {
// TODO: add tests to find all cards from https://mtg.wiki/page/Land_destruction
// TODO: add tests
/*
// mass land destruction
Ajani Vengeant
Armageddon
Avalanche
Bend or Break
Boil
Boiling Seas
Boom // Bust
Burning of Xinye
Catastrophe
Decree of Annihilation
Desolation Angel
Devastation
Fall of the Thran
From the Ashes
Impending Disaster
Jokulhaups
Myojin of Infinite Rage
Numot, the Devastator
Obliterate
Orcish Settlers
Ravages of War
Ruination
Rumbling Crescendo
Scorched Earth
Tsunami
Wake of Destruction
Wildfire
*/
return lowerText.contains("lands")
|| lowerText.contains("plains")
|| lowerText.contains("island")
|| lowerText.contains("swamp")
|| lowerText.contains("mountain")
|| lowerText.contains("forest");
}
}

View file

@ -0,0 +1,76 @@
package mage.client.components;
import mage.cards.decks.Deck;
import mage.client.util.GUISizeHelper;
import mage.client.util.gui.GuiDisplayUtil;
import mage.deck.Commander;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Inject power level info inside validation panel
*
* @author JayDi85
*/
public class EdhPowerLevelLegalityLabel extends LegalityLabel {
private final Commander commanderDeckType = new Commander();
private final List<String> foundPowerCards = new ArrayList<>();
public EdhPowerLevelLegalityLabel() {
super("EDH Power Level: ?", null);
setPreferredSize(DIM_PREFERRED_X3);
}
@Override
public List<String> selectCards() {
// choose cards with power level
return this.foundPowerCards;
}
@Override
public void validateDeck(Deck deck) {
// find and save power level and card hints
List<String> foundInfo = new ArrayList<>();
int level = this.commanderDeckType.getEdhPowerLevel(deck, foundPowerCards, foundInfo);
this.setText(String.format("EDH Power Level: %d", level));
// sort by score "+5 from xxx"
Pattern pattern = Pattern.compile("\\+(\\d+)");
foundInfo.sort((o1, o2) -> {
Matcher matcher = pattern.matcher(o1);
int score1 = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
matcher = pattern.matcher(o2);
int score2 = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
if (score1 != score2) {
return Integer.compare(score2, score1);
}
return o1.compareTo(o2);
});
showStateInfo(formatCardsInfoTooltip(level, foundInfo));
}
private String formatCardsInfoTooltip(int level, List<String> foundInfo) {
// use 60% font for better and compatible list
int infoFontSize = Math.round(GUISizeHelper.cardTooltipFont.getSize() * 0.6f);
int maxLimit = 25;
String extraInfo = this.foundPowerCards.size() <= maxLimit ? "" : String.format("<li style=\"margin-bottom: 2px;\">and %d more cards</li>", this.foundPowerCards.size() - maxLimit);
return foundInfo.stream()
.limit(maxLimit)
.reduce("<html><body>"
+ "<p>EDH Power Level: <span style='color:#b8860b;font-weight:bold;'>" + level + "</span></p>"
+ "<br>"
+ "<u>Found <span style='font-weight:bold;'>" + this.foundPowerCards.size() + "</span> cards with power levels (click to select it)</u>"
+ "<br>"
+ "<ul style=\"font-size: " + infoFontSize + "px; width: " + TOOLTIP_TABLE_WIDTH + "px; padding-left: 10px; margin: 0;\">",
(str, info) -> str + String.format("<li style=\"margin-bottom: 2px;\">%s</li>", info), String::concat)
+ extraInfo
+ "</ul>"
+ "</body></html>";
}
}

View file

@ -3,17 +3,18 @@ package mage.client.components;
import mage.cards.decks.Deck; import mage.cards.decks.Deck;
import mage.cards.decks.DeckValidator; import mage.cards.decks.DeckValidator;
import mage.cards.decks.DeckValidatorError; import mage.cards.decks.DeckValidatorError;
import mage.cards.decks.importer.DeckImporter;
import org.unbescape.html.HtmlEscape; import org.unbescape.html.HtmlEscape;
import org.unbescape.html.HtmlEscapeLevel; import org.unbescape.html.HtmlEscapeLevel;
import org.unbescape.html.HtmlEscapeType; import org.unbescape.html.HtmlEscapeType;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.File; import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* @author Elandril * @author Elandril, JayDi85
*/ */
public class LegalityLabel extends JLabel { public class LegalityLabel extends JLabel {
@ -25,8 +26,10 @@ public class LegalityLabel extends JLabel {
protected static final Dimension DIM_MINIMUM = new Dimension(75, 25); protected static final Dimension DIM_MINIMUM = new Dimension(75, 25);
protected static final Dimension DIM_MAXIMUM = new Dimension(150, 75); protected static final Dimension DIM_MAXIMUM = new Dimension(150, 75);
protected static final Dimension DIM_PREFERRED = new Dimension(75, 25); protected static final Dimension DIM_PREFERRED = new Dimension(75, 25);
protected static final Dimension DIM_PREFERRED_X2 = new Dimension(DIM_PREFERRED.width * 2 + 5, 25);
protected static final Dimension DIM_PREFERRED_X3 = new Dimension(DIM_PREFERRED.width * 3 + 5 + 5, 25);
protected static final int TOOLTIP_TABLE_WIDTH = 300; // size of the label's tooltip protected static final int TOOLTIP_TABLE_WIDTH = 400; // size of the label's tooltip
protected static final int TOOLTIP_MAX_ERRORS = 20; // max errors to show in tooltip protected static final int TOOLTIP_MAX_ERRORS = 20; // max errors to show in tooltip
protected Deck currentDeck; protected Deck currentDeck;
@ -92,19 +95,6 @@ public class LegalityLabel extends JLabel {
return button; return button;
} }
public String getErrorMessage() {
return errorMessage;
}
public DeckValidator getValidator() {
return validator;
}
public void setValidator(DeckValidator validator) {
this.validator = validator;
revalidateDeck();
}
protected String escapeHtml(String string) { protected String escapeHtml(String string) {
return HtmlEscape.escapeHtml(string, HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, HtmlEscapeLevel.LEVEL_0_ONLY_MARKUP_SIGNIFICANT_EXCEPT_APOS); return HtmlEscape.escapeHtml(string, HtmlEscapeType.HTML4_NAMED_REFERENCES_DEFAULT_TO_HEXA, HtmlEscapeLevel.LEVEL_0_ONLY_MARKUP_SIGNIFICANT_EXCEPT_APOS);
} }
@ -146,25 +136,33 @@ public class LegalityLabel extends JLabel {
setBackground(color); setBackground(color);
} }
public void showState(Color color, String tooltip) { public void showState(Color color, String tooltip, boolean useErrors) {
setBackground(color); setBackground(color);
if (useErrors) {
setToolTipText(appendErrorMessage(tooltip)); setToolTipText(appendErrorMessage(tooltip));
} else {
setToolTipText(tooltip);
}
}
public void showStateInfo(String tooltip) {
showState(COLOR_LEGAL, tooltip, false);
} }
public void showStateUnknown(String tooltip) { public void showStateUnknown(String tooltip) {
showState(COLOR_UNKNOWN, tooltip); showState(COLOR_UNKNOWN, tooltip, true);
} }
public void showStateLegal(String tooltip) { public void showStateLegal(String tooltip) {
showState(COLOR_LEGAL, tooltip); showState(COLOR_LEGAL, tooltip, true);
} }
public void showStatePartlyLegal(String tooltip) { public void showStatePartlyLegal(String tooltip) {
showState(COLOR_PARTLY_LEGAL, tooltip); showState(COLOR_PARTLY_LEGAL, tooltip, true);
} }
public void showStateNotLegal(String tooltip) { public void showStateNotLegal(String tooltip) {
showState(COLOR_NOT_LEGAL, tooltip); showState(COLOR_NOT_LEGAL, tooltip, true);
} }
public void validateDeck(Deck deck) { public void validateDeck(Deck deck) {
@ -191,28 +189,13 @@ public class LegalityLabel extends JLabel {
} }
} }
public void validateDeck(File deckFile) { public java.util.List<String> selectCards() {
deckFile = deckFile.getAbsoluteFile(); if (this.validator == null) {
if (!deckFile.exists()) { return Collections.emptyList();
errorMessage = String.format("Deck file '%s' does not exist.", deckFile.getAbsolutePath());
showStateUnknown("<html><body><b>No Deck loaded!</b></body></html>");
return;
} }
try { return this.validator.getErrorsList().stream()
StringBuilder errorMessages = new StringBuilder(); .map(DeckValidatorError::getCardName)
Deck deck = Deck.load(DeckImporter.importDeckFromFile(deckFile.getAbsolutePath(), errorMessages, false), true, true); .filter(Objects::nonNull)
errorMessage = errorMessages.toString(); .collect(Collectors.toList());
validateDeck(deck);
} catch (Exception ex) {
errorMessage = String.format("Error importing deck from file '%s'!", deckFile.getAbsolutePath());
}
}
public void revalidateDeck() {
validateDeck(currentDeck);
}
public void validateDeck(String deckFile) {
validateDeck(new File(deckFile));
} }
} }

View file

@ -5,20 +5,21 @@ import mage.cards.decks.Deck;
import mage.cards.repository.CardCriteria; import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo; import mage.cards.repository.CardInfo;
import mage.cards.repository.ExpansionRepository; import mage.cards.repository.ExpansionRepository;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.sets.ConstructedFormats; import mage.client.util.sets.ConstructedFormats;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.ColoredManaSymbol; import mage.constants.ColoredManaSymbol;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/** /**
* Generates random card pool and builds a deck. * Generates random card pool and builds a deck.
* *
* @author nantuko * @author nantuko, Simown, JayDi85
* @author Simown
*/ */
public final class DeckGenerator { public final class DeckGenerator {
@ -124,7 +125,7 @@ public final class DeckGenerator {
genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(), genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(),
genDialog.getLandPercentage(), allowedColors, genDialog.isSingleton(), genDialog.isColorless(), genDialog.getLandPercentage(), allowedColors, genDialog.isSingleton(), genDialog.isColorless(),
genDialog.isAdvanced(), genDialog.getDeckGeneratorCMC()); genDialog.isCommander(), genDialog.isAdvanced(), genDialog.getDeckGeneratorCMC());
final String[] sets = setsToUse.toArray(new String[setsToUse.size()]); final String[] sets = setsToUse.toArray(new String[setsToUse.size()]);
@ -155,8 +156,8 @@ public final class DeckGenerator {
// Generate basic land cards // Generate basic land cards
Map<String, List<CardInfo>> basicLands = DeckGeneratorPool.generateBasicLands(setsToUse); Map<String, List<CardInfo>> basicLands = DeckGeneratorPool.generateBasicLands(setsToUse);
DeckGeneratorPool.generateSpells(creatureCriteria, genPool.getCreatureCount()); DeckGeneratorPool.generateSpells(creatureCriteria, genPool.getCreatureCount(), genPool.getCommandersCount());
DeckGeneratorPool.generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount()); DeckGeneratorPool.generateSpells(nonCreatureCriteria, genPool.getNonCreatureCount(), 0);
DeckGeneratorPool.generateLands(genDialog.useNonBasicLand(), nonBasicLandCriteria, basicLands); DeckGeneratorPool.generateLands(genDialog.useNonBasicLand(), nonBasicLandCriteria, basicLands);
// Reconstructs the final deck and adjusts for Math rounding and/or missing cards // Reconstructs the final deck and adjusts for Math rounding and/or missing cards

View file

@ -1,46 +1,76 @@
package mage.client.deck.generator; package mage.client.deck.generator;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
/**
* Mana value distribution between cards in diff deck sizes
*/
public enum DeckGeneratorCMC { public enum DeckGeneratorCMC {
Low(ImmutableList.<CMC>builder() Low(
// 100
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.55f))
.add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.15f)).build(),
// 60
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.60f)) .add(new CMC(0, 2, 0.60f))
.add(new CMC(3, 4, 0.30f)) .add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.10f)).build(), .add(new CMC(5, 6, 0.10f)).build(),
// 40
ImmutableList.<CMC>builder() ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.65f)) .add(new CMC(0, 2, 0.65f))
.add(new CMC(3, 4, 0.30f)) .add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 5, 0.05f)).build()), .add(new CMC(5, 5, 0.05f)).build()),
Default(ImmutableList.<CMC>builder() Default(
// 100
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.15f))
.add(new CMC(3, 5, 0.50f))
.add(new CMC(6, 7, 0.30f))
.add(new CMC(8, 100, 0.05f)).build(),
// 60
ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.20f)) .add(new CMC(0, 2, 0.20f))
.add(new CMC(3, 5, 0.50f)) .add(new CMC(3, 5, 0.50f))
.add(new CMC(6, 7, 0.25f)) .add(new CMC(6, 7, 0.25f))
.add(new CMC(8, 100, 0.05f)).build(), .add(new CMC(8, 100, 0.05f)).build(),
// 40
ImmutableList.<CMC>builder() ImmutableList.<CMC>builder()
.add(new CMC(0, 2, 0.30f)) .add(new CMC(0, 2, 0.30f))
.add(new CMC(3, 4, 0.45f)) .add(new CMC(3, 4, 0.45f))
.add(new CMC(5, 6, 0.20f)) .add(new CMC(5, 6, 0.20f))
.add(new CMC(7, 100, 0.05f)).build()), .add(new CMC(7, 100, 0.05f)).build()),
High(ImmutableList.<CMC>builder(). High(
// 100
ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.05f))
.add(new CMC(3, 5, 0.40f))
.add(new CMC(6, 7, 0.40f))
.add(new CMC(8, 100, 0.15f)).build(),
// 60
ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.05f)) add(new CMC(0, 2, 0.05f))
.add(new CMC(3, 5, 0.35f)) .add(new CMC(3, 5, 0.35f))
.add(new CMC(6, 7, 0.40f)) .add(new CMC(6, 7, 0.40f))
.add(new CMC(8, 100, 0.15f)).build(), .add(new CMC(8, 100, 0.15f)).build(),
// 40
ImmutableList.<CMC>builder(). ImmutableList.<CMC>builder().
add(new CMC(0, 2, 0.10f)) add(new CMC(0, 2, 0.10f))
.add(new CMC(3, 4, 0.30f)) .add(new CMC(3, 4, 0.30f))
.add(new CMC(5, 6, 0.45f)) .add(new CMC(5, 6, 0.45f))
.add(new CMC(7, 100, 0.15f)).build()); .add(new CMC(7, 100, 0.15f)).build());
private final List<CMC> poolCMCs100;
private final List<CMC> poolCMCs60; private final List<CMC> poolCMCs60;
private final List<CMC> poolCMCs40; private final List<CMC> poolCMCs40;
DeckGeneratorCMC(List<CMC> CMCs60, List<CMC> CMCs40) { DeckGeneratorCMC(List<CMC> CMCs100, List<CMC> CMCs60, List<CMC> CMCs40) {
this.poolCMCs100 = CMCs100;
this.poolCMCs60 = CMCs60; this.poolCMCs60 = CMCs60;
this.poolCMCs40 = CMCs40; this.poolCMCs40 = CMCs40;
} }
@ -53,6 +83,10 @@ public enum DeckGeneratorCMC {
return this.poolCMCs60; return this.poolCMCs60;
} }
public List<CMC> get100CardPoolCMC() {
return this.poolCMCs100;
}
static class CMC { static class CMC {
public final int min; public final int min;
public final int max; public final int max;

View file

@ -32,7 +32,7 @@ public class DeckGeneratorDialog {
private static String selectedColors; private static String selectedColors;
private static JComboBox cbSets, cbDeckSize, cbCMC; private static JComboBox cbSets, cbDeckSize, cbCMC;
private static JButton btnGenerate, btnCancel, btnReset; private static JButton btnGenerate, btnCancel, btnReset;
private static JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless, cAdvanced; private static JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless, cAdvanced, cCommander;
private static JLabel averageCMCLabel; private static JLabel averageCMCLabel;
private static SimpleDateFormat dateFormat; private static SimpleDateFormat dateFormat;
private static RatioAdjustingSliderPanel adjustingSliderPanel; private static RatioAdjustingSliderPanel adjustingSliderPanel;
@ -63,7 +63,7 @@ public class DeckGeneratorDialog {
c.insets = new Insets(5, 10, 0, 10); c.insets = new Insets(5, 10, 0, 10);
c.gridx = 1; c.gridx = 1;
c.gridy = 0; c.gridy = 0;
String chosen = MageFrame.getPreferences().get("genDeckColor", "u"); String chosen = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORS, "u");
final ColorsChooser colorsChooser = new ColorsChooser(chosen); final ColorsChooser colorsChooser = new ColorsChooser(chosen);
mainPanel.add(colorsChooser, c); mainPanel.add(colorsChooser, c);
@ -135,7 +135,7 @@ public class DeckGeneratorDialog {
c.ipadx = 30; c.ipadx = 30;
c.insets = new Insets(5, 10, 0, 10); c.insets = new Insets(5, 10, 0, 10);
c.weightx = 0.90; c.weightx = 0.90;
cbDeckSize = new JComboBox<>(new String[]{"40", "60"}); cbDeckSize = new JComboBox<>(new String[]{"40", "60", "100"});
cbDeckSize.setSelectedIndex(0); cbDeckSize.setSelectedIndex(0);
cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT); cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT);
mainPanel.add(cbDeckSize, c); mainPanel.add(cbDeckSize, c);
@ -148,31 +148,33 @@ public class DeckGeneratorDialog {
JPanel jCheckBoxes = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel jCheckBoxes = new JPanel(new FlowLayout(FlowLayout.LEFT));
// Singletons // Singletons
boolean commanderEnabled = Boolean.parseBoolean(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COMMANDER, "false"));
cSingleton = new JCheckBox("Singleton", false); cSingleton = new JCheckBox("Singleton", false);
cSingleton.setToolTipText("Allow only a single copy of each non-land card in your deck."); cSingleton.setToolTipText("Allow only a single copy of each non-land card in your deck.");
String singletonEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SINGLETON, "false"); String singletonEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SINGLETON, "false");
cSingleton.setSelected(Boolean.valueOf(singletonEnabled)); cSingleton.setSelected(Boolean.parseBoolean(singletonEnabled));
jCheckBoxes.add(cSingleton); jCheckBoxes.add(cSingleton);
cSingleton.setEnabled(!commanderEnabled);
// Artifacts // Artifacts
cArtifacts = new JCheckBox("Artifacts", false); cArtifacts = new JCheckBox("Artifacts", false);
cArtifacts.setToolTipText("Use artifacts and artifact creatures in your deck."); cArtifacts.setToolTipText("Use artifacts and artifact creatures in your deck.");
String artifactEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, "false"); String artifactEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, "false");
cArtifacts.setSelected(Boolean.valueOf(artifactEnabled)); cArtifacts.setSelected(Boolean.parseBoolean(artifactEnabled));
jCheckBoxes.add(cArtifacts); jCheckBoxes.add(cArtifacts);
// Non-basic lands // Non-basic lands
cNonBasicLands = new JCheckBox("Non-basic Lands", false); cNonBasicLands = new JCheckBox("Non-basic Lands", false);
cNonBasicLands.setToolTipText("Use non-basic lands in your deck (if applicable)."); cNonBasicLands.setToolTipText("Use non-basic lands in your deck (if applicable).");
String nonBasicEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS, "false"); String nonBasicEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS, "false");
cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled)); cNonBasicLands.setSelected(Boolean.parseBoolean(nonBasicEnabled));
jCheckBoxes.add(cNonBasicLands); jCheckBoxes.add(cNonBasicLands);
// Colorless mana // Colorless mana
cColorless = new JCheckBox("Colorless mana", false); cColorless = new JCheckBox("Colorless mana", false);
cColorless.setToolTipText("Allow cards with colorless mana cost."); cColorless.setToolTipText("Allow cards with colorless mana cost.");
String colorlessEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, "false"); String colorlessEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, "false");
cColorless.setSelected(Boolean.valueOf(colorlessEnabled)); cColorless.setSelected(Boolean.parseBoolean(colorlessEnabled));
jCheckBoxes.add(cColorless); jCheckBoxes.add(cColorless);
c.ipadx = 0; c.ipadx = 0;
c.gridx = 0; c.gridx = 0;
@ -181,12 +183,28 @@ public class DeckGeneratorDialog {
c.gridwidth = 3; c.gridwidth = 3;
mainPanel.add(jCheckBoxes, c); mainPanel.add(jCheckBoxes, c);
// Commander
cCommander = new JCheckBox("Commander", false);
cCommander.setToolTipText("Add legendary creature as commander");
cCommander.setSelected(commanderEnabled);
jCheckBoxes.add(cCommander);
c.ipadx = 0;
c.gridx = 0;
c.gridy = 3;
c.weightx = 1;
c.gridwidth = 3;
mainPanel.add(jCheckBoxes, c);
cCommander.addItemListener(itemEvent -> {
// commander require singletone mode
cSingleton.setEnabled(!cCommander.isSelected());
});
// Create the advanced configuration panel // Create the advanced configuration panel
JPanel advancedPanel = createAdvancedPanel(); JPanel advancedPanel = createAdvancedPanel();
// Advanced checkbox (enable/disable advanced configuration) // Advanced checkbox (enable/disable advanced configuration)
cAdvanced = new JCheckBox("Advanced"); cAdvanced = new JCheckBox("Customize distribution");
cAdvanced.setToolTipText("Enable advanced configuration options"); cAdvanced.setToolTipText("Customize cards distribution due mana values and types");
cAdvanced.addItemListener(itemEvent -> { cAdvanced.addItemListener(itemEvent -> {
boolean enable = cAdvanced.isSelected(); boolean enable = cAdvanced.isSelected();
enableAdvancedPanel(enable); enableAdvancedPanel(enable);
@ -194,7 +212,7 @@ public class DeckGeneratorDialog {
// Advanced Checkbox // Advanced Checkbox
String advancedSavedValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, "false"); String advancedSavedValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, "false");
boolean advancedEnabled = Boolean.valueOf(advancedSavedValue); boolean advancedEnabled = Boolean.parseBoolean(advancedSavedValue);
enableAdvancedPanel(advancedEnabled); enableAdvancedPanel(advancedEnabled);
cAdvanced.setSelected(advancedEnabled); cAdvanced.setSelected(advancedEnabled);
c.gridy = 4; c.gridy = 4;
@ -212,7 +230,7 @@ public class DeckGeneratorDialog {
colorsChooser.setEnabled(false); colorsChooser.setEnabled(false);
selectedColors = (String) colorsChooser.getSelectedItem(); selectedColors = (String) colorsChooser.getSelectedItem();
dlg.setVisible(false); dlg.setVisible(false);
MageFrame.getPreferences().put("genDeckColor", selectedColors); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORS, selectedColors);
}); });
btnCancel = new JButton("Cancel"); btnCancel = new JButton("Cancel");
btnCancel.addActionListener(e -> { btnCancel.addActionListener(e -> {
@ -297,7 +315,7 @@ public class DeckGeneratorDialog {
c.gridwidth = 1; c.gridwidth = 1;
c.gridy = 2; c.gridy = 2;
btnReset = new JButton("Reset"); btnReset = new JButton("Reset");
btnReset.setToolTipText("Reset advanced dialog to default values"); btnReset.setToolTipText("Reset custom cards distribution to default values");
btnReset.addActionListener(actionEvent -> { btnReset.addActionListener(actionEvent -> {
cbCMC.setSelectedItem(DeckGeneratorCMC.Default); cbCMC.setSelectedItem(DeckGeneratorCMC.Default);
adjustingSliderPanel.resetValues(); adjustingSliderPanel.resetValues();
@ -374,6 +392,12 @@ public class DeckGeneratorDialog {
return selected; return selected;
} }
public boolean isCommander() {
boolean selected = cCommander.isSelected();
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COMMANDER, Boolean.toString(selected));
return selected;
}
public boolean isAdvanced() { public boolean isAdvanced() {
boolean selected = cAdvanced.isSelected(); boolean selected = cAdvanced.isSelected();
PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, Boolean.toString(selected)); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, Boolean.toString(selected));

View file

@ -1,6 +1,6 @@
package mage.client.deck.generator; package mage.client.deck.generator;
import mage.ObjectColor;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.decks.Deck; import mage.cards.decks.Deck;
@ -11,16 +11,17 @@ import mage.constants.ColoredManaSymbol;
import mage.constants.Rarity; import mage.constants.Rarity;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import mage.util.TournamentUtil; import mage.util.TournamentUtil;
import org.apache.log4j.Logger;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
/** /**
* * @author Simown, JayDi85
* @author Simown
*/ */
public class DeckGeneratorPool public class DeckGeneratorPool {
{
private static final Logger logger = Logger.getLogger(DeckGeneratorPool.class);
public static final int DEFAULT_CREATURE_PERCENTAGE = 38; public static final int DEFAULT_CREATURE_PERCENTAGE = 38;
public static final int DEFAULT_NON_CREATURE_PERCENTAGE = 21; public static final int DEFAULT_NON_CREATURE_PERCENTAGE = 21;
@ -31,6 +32,7 @@ public class DeckGeneratorPool
private final List<DeckGeneratorCMC.CMC> poolCMCs; private final List<DeckGeneratorCMC.CMC> poolCMCs;
private final int creatureCount; private final int creatureCount;
private final int nonCreatureCount; private final int nonCreatureCount;
private final int commandersCount;
private final int landCount; private final int landCount;
private final boolean isSingleton; private final boolean isSingleton;
private final int deckSize; private final int deckSize;
@ -56,15 +58,17 @@ public class DeckGeneratorPool
* @param isSingleton if the deck only has 1 copy of each non-land card. * @param isSingleton if the deck only has 1 copy of each non-land card.
* @param colorlessAllowed if colourless mana symbols are allowed in costs in the deck. * @param colorlessAllowed if colourless mana symbols are allowed in costs in the deck.
* @param isAdvanced if the user has provided advanced options to generate the deck. * @param isAdvanced if the user has provided advanced options to generate the deck.
* @param isCommander reserve commander card
* @param deckGeneratorCMC the CMC curve to use for this deck * @param deckGeneratorCMC the CMC curve to use for this deck
*/ */
public DeckGeneratorPool(final int deckSize, final int creaturePercentage, final int nonCreaturePercentage, final int landPercentage, public DeckGeneratorPool(final int deckSize, final int creaturePercentage, final int nonCreaturePercentage, final int landPercentage,
final List<ColoredManaSymbol> allowedColors, boolean isSingleton, boolean colorlessAllowed, boolean isAdvanced, DeckGeneratorCMC deckGeneratorCMC) final List<ColoredManaSymbol> allowedColors, boolean isSingleton, boolean colorlessAllowed, boolean isCommander,
{ boolean isAdvanced, DeckGeneratorCMC deckGeneratorCMC) {
this.deckSize = deckSize; this.deckSize = deckSize;
this.allowedColors = allowedColors; this.allowedColors = allowedColors;
this.isSingleton = isSingleton;
this.colorlessAllowed = colorlessAllowed; this.colorlessAllowed = colorlessAllowed;
this.commandersCount = isCommander ? 1 : 0;
this.isSingleton = isSingleton || isCommander; // commander must use singleton mode only
this.deck = new Deck(); this.deck = new Deck();
@ -73,32 +77,48 @@ public class DeckGeneratorPool
this.creatureCount = (int) Math.ceil((deckSize / 100.0) * creaturePercentage); this.creatureCount = (int) Math.ceil((deckSize / 100.0) * creaturePercentage);
this.nonCreatureCount = (int) Math.ceil((deckSize / 100.0) * nonCreaturePercentage); this.nonCreatureCount = (int) Math.ceil((deckSize / 100.0) * nonCreaturePercentage);
this.landCount = (int) Math.ceil((deckSize / 100.0) * landPercentage); this.landCount = (int) Math.ceil((deckSize / 100.0) * landPercentage);
if(this.deckSize == 60) { switch (this.deckSize) {
case 100:
this.poolCMCs = deckGeneratorCMC.get100CardPoolCMC();
break;
case 60:
this.poolCMCs = deckGeneratorCMC.get60CardPoolCMC(); this.poolCMCs = deckGeneratorCMC.get60CardPoolCMC();
} else { break;
case 40:
this.poolCMCs = deckGeneratorCMC.get40CardPoolCMC(); this.poolCMCs = deckGeneratorCMC.get40CardPoolCMC();
break;
default:
throw new IllegalArgumentException("Unsupported deck size: " + this.deckSize);
} }
} else { } else {
// Ignore the advanced group, just use defaults // Ignore the advanced group, just use defaults
this.creatureCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_CREATURE_PERCENTAGE); this.creatureCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_CREATURE_PERCENTAGE);
this.nonCreatureCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_NON_CREATURE_PERCENTAGE); this.nonCreatureCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_NON_CREATURE_PERCENTAGE);
this.landCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_LAND_PERCENTAGE); this.landCount = (int) Math.ceil((deckSize / 100.0) * DEFAULT_LAND_PERCENTAGE);
if(this.deckSize == 60) { switch (this.deckSize) {
case 100:
this.poolCMCs = DeckGeneratorCMC.Default.get100CardPoolCMC();
break;
case 60:
this.poolCMCs = DeckGeneratorCMC.Default.get60CardPoolCMC(); this.poolCMCs = DeckGeneratorCMC.Default.get60CardPoolCMC();
} else { break;
case 40:
this.poolCMCs = DeckGeneratorCMC.Default.get40CardPoolCMC(); this.poolCMCs = DeckGeneratorCMC.Default.get40CardPoolCMC();
break;
default:
throw new IllegalArgumentException("Unsupported deck size: " + this.deckSize);
} }
} }
if (allowedColors.size() == 1) { if (allowedColors.size() == 1) {
monoColored = true; monoColored = true;
} }
} }
/** /**
* Adjusts the number of spell cards that should be in a converted mana cost (CMC) range, given the amount of cards total. * Adjusts the number of spell cards that should be in a converted mana cost (CMC) range, given the amount of cards total.
*
* @param cardsCount the number of total cards. * @param cardsCount the number of total cards.
* @return a list of CMC ranges, with the amount of cards for each CMC range * @return a list of CMC ranges, with the amount of cards for each CMC range
*/ */
@ -115,11 +135,11 @@ public class DeckGeneratorPool
* Verifies if the spell card supplied is valid for this pool of cards. * Verifies if the spell card supplied is valid for this pool of cards.
* Checks that there isn't too many copies of this card in the deck. * Checks that there isn't too many copies of this card in the deck.
* Checks that the card fits the chosen colors for this pool. * Checks that the card fits the chosen colors for this pool.
*
* @param card the spell card * @param card the spell card
* @return if the spell card is valid for this pool. * @return if the spell card is valid for this pool.
*/ */
public boolean isValidSpellCard(Card card) public boolean isValidSpellCard(Card card) {
{
int cardCount = getCardCount((card.getName())); int cardCount = getCardCount((card.getName()));
// Check it hasn't already got the maximum number of copies in a deck // Check it hasn't already got the maximum number of copies in a deck
if (cardCount < (isSingleton ? 1 : 4)) { if (cardCount < (isSingleton ? 1 : 4)) {
@ -132,11 +152,11 @@ public class DeckGeneratorPool
/** /**
* Verifies if the non-basic land card supplied is valid for this pool of cards. * Verifies if the non-basic land card supplied is valid for this pool of cards.
*
* @param card the non-basic land card * @param card the non-basic land card
* @return if the land card generates the allowed colors for this pool. * @return if the land card generates the allowed colors for this pool.
*/ */
public boolean isValidLandCard(Card card) public boolean isValidLandCard(Card card) {
{
int cardCount = getCardCount((card.getName())); int cardCount = getCardCount((card.getName()));
// No need to check if the land is valid for the colors chosen // No need to check if the land is valid for the colors chosen
// They are all filtered before searching for lands to include in the deck. // They are all filtered before searching for lands to include in the deck.
@ -146,10 +166,10 @@ public class DeckGeneratorPool
/** /**
* Adds a card to the pool and updates the count of this card. * Adds a card to the pool and updates the count of this card.
*
* @param card the card to add. * @param card the card to add.
*/ */
public void addCard(Card card) public void addCard(Card card) {
{
Object cnt = cardCounts.get((card.getName())); Object cnt = cardCounts.get((card.getName()));
if (cnt == null) if (cnt == null)
cardCounts.put(card.getName(), 0); cardCounts.put(card.getName(), 0);
@ -158,10 +178,19 @@ public class DeckGeneratorPool
deckCards.add(card); deckCards.add(card);
} }
public void clearCards(boolean isClearReserve) {
cardCounts.clear();
deckCards.clear();
if (isClearReserve) {
reserveSpells.clear();
}
}
/** /**
* Adds a card to the reserve pool. * Adds a card to the reserve pool.
* Reserve pool is used when the deck generation fails to build a complete deck, or * Reserve pool is used when the deck generation fails to build a complete deck, or
* a partially complete deck (e.g. if there are no cards found that match a CMC) * a partially complete deck (e.g. if there are no cards found that match a CMC)
*
* @param card the card to add * @param card the card to add
* @param cardCMC the converted mana cost of the card * @param cardCMC the converted mana cost of the card
*/ */
@ -176,30 +205,17 @@ public class DeckGeneratorPool
return false; return false;
} }
/**
* Checks if the mana symbols in the card all match the allowed colors for this pool.
* @param card the spell card to check.
* @return if all the mana symbols fit the chosen colors.
*/
private boolean cardFitsChosenColors(Card card) { private boolean cardFitsChosenColors(Card card) {
for (String symbol : card.getManaCostSymbols()) { Set<String> needColors = allowedColors.stream().map(ColoredManaSymbol::toString).collect(Collectors.toSet());
boolean found = false; List<ObjectColor> cardColors = card.getColorIdentity().getColors();
symbol = symbol.replace("{", "").replace("}", ""); for (ObjectColor cardColor : cardColors) {
if (isColoredManaSymbol(symbol)) { if (!needColors.contains(cardColor.toString())) {
for (ColoredManaSymbol allowed : allowedColors) {
if (symbol.contains(allowed.toString())) {
found = true;
break;
}
}
if (!found) {
return false; return false;
} }
} }
if (symbol.equals("C") && !colorlessAllowed) { if (cardColors.isEmpty() && !colorlessAllowed) {
return false; return false;
} }
}
return true; return true;
} }
@ -208,6 +224,7 @@ public class DeckGeneratorPool
* Calculates the percentage of colored mana symbols over all spell cards in the deck. * Calculates the percentage of colored mana symbols over all spell cards in the deck.
* Used to balance the generation of basic lands so the amount of lands matches the * Used to balance the generation of basic lands so the amount of lands matches the
* cards mana costs. * cards mana costs.
*
* @return a list of colored mana symbols and the percentage of symbols seen in cards mana costs. * @return a list of colored mana symbols and the percentage of symbols seen in cards mana costs.
*/ */
public Map<String, Double> calculateSpellColorPercentages() { public Map<String, Double> calculateSpellColorPercentages() {
@ -248,11 +265,11 @@ public class DeckGeneratorPool
/** /**
* Calculates how many of each mana the non-basic lands produce. * Calculates how many of each mana the non-basic lands produce.
*
* @param deckLands the non-basic lands which will be used in the deck. * @param deckLands the non-basic lands which will be used in the deck.
* @return a mapping of colored mana symbol to the amount that can be produced. * @return a mapping of colored mana symbol to the amount that can be produced.
*/ */
public Map<String,Integer> countManaProduced(List<Card> deckLands) public Map<String, Integer> countManaProduced(List<Card> deckLands) {
{
Map<String, Integer> manaCounts = new HashMap<>(); Map<String, Integer> manaCounts = new HashMap<>();
for (final ColoredManaSymbol color : ColoredManaSymbol.values()) { for (final ColoredManaSymbol color : ColoredManaSymbol.values()) {
manaCounts.put(color.toString(), 0); manaCounts.put(color.toString(), 0);
@ -271,7 +288,9 @@ public class DeckGeneratorPool
return manaCounts; return manaCounts;
} }
/** Filter all the non-basic lands retrieved from the database. /**
* Filter all the non-basic lands retrieved from the database.
*
* @param landCardsInfo information about all the cards. * @param landCardsInfo information about all the cards.
* @return a list of cards that produce the allowed colors for this pool. * @return a list of cards that produce the allowed colors for this pool.
*/ */
@ -288,6 +307,7 @@ public class DeckGeneratorPool
/** /**
* Returns the card name that represents the basic land for this color. * Returns the card name that represents the basic land for this color.
*
* @param symbolString the colored mana symbol. * @param symbolString the colored mana symbol.
* @return the name of a basic land card. * @return the name of a basic land card.
*/ */
@ -311,25 +331,50 @@ public class DeckGeneratorPool
/** /**
* Returns a complete deck. * Returns a complete deck.
*
* @return the deck. * @return the deck.
*/ */
public Deck getDeck() { public Deck getDeck() {
Set<Card> actualDeck = deck.getCards(); deck.getCards().clear();
actualDeck.addAll(deckCards); deck.getSideboard().clear();
List<Card> useCards = new ArrayList<>(deckCards);
List<Card> useCommanders = new ArrayList<>();
// take random commanders
if (commandersCount > 0) {
List<Card> possibleCommanders = deckCards.stream()
.filter(this::isValidCommander)
.collect(Collectors.toList());
Card nextCommander = RandomUtil.randomFromCollection(possibleCommanders);
while (nextCommander != null && useCommanders.size() < commandersCount) {
useCards.remove(nextCommander);
useCommanders.add(nextCommander);
}
}
deck.getCards().addAll(useCards);
deck.getSideboard().addAll(useCommanders);
return deck; return deck;
} }
/** /**
* Returns the number of creatures needed in this pool. * Returns the number of creatures needed in this pool.
*
* @return the number of creatures. * @return the number of creatures.
*/ */
public int getCreatureCount() { public int getCreatureCount() {
return creatureCount; return creatureCount;
} }
public int getCommandersCount() {
return commandersCount;
}
/** /**
* Returns the number of non-creatures needed in this pool. * Returns the number of non-creatures needed in this pool.
*
* @return the number of non-creatures. * @return the number of non-creatures.
*/ */
public int getNonCreatureCount() { public int getNonCreatureCount() {
@ -338,6 +383,7 @@ public class DeckGeneratorPool
/** /**
* Returns the number of lands (basic + non-basic) needed in this pool. * Returns the number of lands (basic + non-basic) needed in this pool.
*
* @return the number of lands. * @return the number of lands.
*/ */
public int getLandCount() { public int getLandCount() {
@ -346,6 +392,7 @@ public class DeckGeneratorPool
/** /**
* Returns if this pool only uses one color. * Returns if this pool only uses one color.
*
* @return if this pool is monocolored. * @return if this pool is monocolored.
*/ */
public boolean isMonoColoredDeck() { public boolean isMonoColoredDeck() {
@ -354,6 +401,7 @@ public class DeckGeneratorPool
/** /**
* Returns the size of the deck to generate from this pool. * Returns the size of the deck to generate from this pool.
*
* @return the deck size. * @return the deck size.
*/ */
public int getDeckSize() { public int getDeckSize() {
@ -364,10 +412,10 @@ public class DeckGeneratorPool
* Fixes undersized or oversized decks that have been generated. * Fixes undersized or oversized decks that have been generated.
* Removes random cards from an oversized deck until it is the correct size. * Removes random cards from an oversized deck until it is the correct size.
* Uses the reserve pool to fill up and undersized deck with cards. * Uses the reserve pool to fill up and undersized deck with cards.
*
* @return a fixed list of cards for this deck. * @return a fixed list of cards for this deck.
*/ */
private List<Card> getFixedSpells() private List<Card> getFixedSpells() {
{
int spellSize = deckCards.size(); int spellSize = deckCards.size();
int nonLandSize = (deckSize - landCount); int nonLandSize = (deckSize - landCount);
@ -407,7 +455,7 @@ public class DeckGeneratorPool
// Check we have exactly the right amount of cards for a deck. // Check we have exactly the right amount of cards for a deck.
if (deckCards.size() != nonLandSize) { if (deckCards.size() != nonLandSize) {
throw new IllegalStateException("Not enough cards found to generate deck."); logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors");
} }
// Return the fixed amount // Return the fixed amount
return deckCards; return deckCards;
@ -416,6 +464,7 @@ public class DeckGeneratorPool
/** /**
* Returns if this land taps for the given color. * Returns if this land taps for the given color.
* Basic string matching to check the ability adds one of the chosen mana when tapped. * Basic string matching to check the ability adds one of the chosen mana when tapped.
*
* @param ability MockAbility of the land card * @param ability MockAbility of the land card
* @param symbol colored mana symbol. * @param symbol colored mana symbol.
* @return if the ability is tapping to produce the mana the symbol represents. * @return if the ability is tapping to produce the mana the symbol represents.
@ -426,6 +475,7 @@ public class DeckGeneratorPool
/** /**
* Returns if this land will produce the chosen colors for this pool. * Returns if this land will produce the chosen colors for this pool.
*
* @param card a non-basic land card. * @param card a non-basic land card.
* @return if this land card taps to produces the colors chosen. * @return if this land card taps to produces the colors chosen.
*/ */
@ -449,8 +499,25 @@ public class DeckGeneratorPool
return false; return false;
} }
private boolean isValidCommander(Card card) {
// commander must be legendary creature
if (!card.isCreature() || !card.isLegendary()) {
return false;
}
// commander must have all chosen colors
for (ColoredManaSymbol symbol : allowedColors) {
if (!card.getColor().contains(new ObjectColor(symbol.toString()))) {
return false;
}
}
return true;
}
/** /**
* Returns if the symbol is a colored mana symbol. * Returns if the symbol is a colored mana symbol.
*
* @param symbol the symbol to check. * @param symbol the symbol to check.
* @return If it is a basic mana symbol or a hybrid mana symbol. * @return If it is a basic mana symbol or a hybrid mana symbol.
*/ */
@ -470,6 +537,7 @@ public class DeckGeneratorPool
/** /**
* Returns how many of this card is in the pool. * Returns how many of this card is in the pool.
* If there are none in the pool it will initalise the card count. * If there are none in the pool it will initalise the card count.
*
* @param cardName the name of the card to check. * @param cardName the name of the card to check.
* @return the number of cards in the pool of this name. * @return the number of cards in the pool of this name.
*/ */
@ -542,22 +610,49 @@ public class DeckGeneratorPool
* reasonable mix of both. * reasonable mix of both.
* *
* @param criteria the criteria to search for in the database. * @param criteria the criteria to search for in the database.
* @param spellCount the number of spells that match the criteria needed in * @param needCardsCount the number of spells that match the criteria needed in
* the deck. * the deck.
* @param needCommandersCount make sure it contains commander creature (must be uses on first generateSpells only)
*/ */
protected static void generateSpells(CardCriteria criteria, int spellCount) { protected static void generateSpells(CardCriteria criteria, int needCardsCount, int needCommandersCount) {
DeckGeneratorPool genPool = DeckGenerator.genPool; DeckGeneratorPool genPool = DeckGenerator.genPool;
if (needCommandersCount > 0 && !genPool.cardCounts.isEmpty()) {
throw new IllegalArgumentException("Wrong code usage: generateSpells with creatures and commanders must be called as first");
}
List<CardInfo> cardPool = CardRepository.instance.findCards(criteria); List<CardInfo> cardPool = CardRepository.instance.findCards(criteria);
int retrievedCount = cardPool.size(); List<DeckGeneratorCMC.CMC> deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
List<DeckGeneratorCMC.CMC> deckCMCs = genPool.getCMCsForSpellCount(spellCount);
int count = 0; int count = 0;
int validCommanders = 0;
int reservesAdded = 0; int reservesAdded = 0;
boolean added; if (cardPool.size() > 0 && cardPool.size() >= needCardsCount) {
if (retrievedCount > 0 && retrievedCount >= spellCount) {
int tries = 0; int tries = 0;
while (count < spellCount) { while (true) {
Card card = cardPool.get(RandomUtil.nextInt(retrievedCount)).createMockCard(); tries++;
if (genPool.isValidSpellCard(card)) {
// can't finish deck, stop and use reserved cards later
if (tries > DeckGenerator.MAX_TRIES) {
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors");
break;
}
// can finish deck - but make sure it has commander
if (count >= needCardsCount) {
if (validCommanders < needCommandersCount) {
// reset deck search from scratch (except reserved cards)
count = 0;
validCommanders = 0;
deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
genPool.clearCards(false);
continue;
}
break;
}
Card card = cardPool.get(RandomUtil.nextInt(cardPool.size())).createMockCard();
if (!genPool.isValidSpellCard(card)) {
continue;
}
int cardCMC = card.getManaValue(); int cardCMC = card.getManaValue();
for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) { for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) {
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) { if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
@ -566,21 +661,18 @@ public class DeckGeneratorPool
deckCMC.setAmount(currentAmount - 1); deckCMC.setAmount(currentAmount - 1);
genPool.addCard(card.copy()); genPool.addCard(card.copy());
count++; count++;
// make sure it has compatible commanders
if (genPool.isValidCommander(card)) {
validCommanders++;
}
} }
} else if (reservesAdded < (genPool.getDeckSize() / 2)) { } else if (reservesAdded < (genPool.getDeckSize() / 2)) {
added = genPool.tryAddReserve(card, cardCMC); if (genPool.tryAddReserve(card, cardCMC)) {
if (added) {
reservesAdded++; reservesAdded++;
} }
} }
} }
} }
tries++;
if (tries > DeckGenerator.MAX_TRIES) {
// Break here, we'll fill in random missing ones later
break;
}
}
} else { } else {
throw new IllegalStateException("Not enough cards to generate deck."); throw new IllegalStateException("Not enough cards to generate deck.");
} }

View file

@ -780,7 +780,7 @@
</Component> </Component>
<Component class="javax.swing.JTextField" name="jTextFieldSearch"> <Component class="javax.swing.JTextField" name="jTextFieldSearch">
<Properties> <Properties>
<Property name="toolTipText" type="java.lang.String" value="Searches for card names and in the rule text of the card."/> <Property name="toolTipText" type="java.lang.String" value="Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)"/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JCheckBox" name="chkNames"> <Component class="javax.swing.JCheckBox" name="chkNames">

View file

@ -1085,7 +1085,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene
} }
}); });
jTextFieldSearch.setToolTipText("Searches for card names and in the rule text of the card."); jTextFieldSearch.setToolTipText("Search cards by any data like name or mana symbols like {W}, {U}, {C}, etc (use quotes for exact search)");
chkNames.setSelected(true); chkNames.setSelected(true);
chkNames.setText("Names"); chkNames.setText("Names");

View file

@ -110,7 +110,7 @@ public class DeckArea extends javax.swing.JPanel {
sideboardList.deselectAll(); sideboardList.deselectAll();
for (CardView card : cards) { for (CardView card : cards) {
CardView newCard = new CardView(card); CardView newCard = new CardView(card);
deckList.addCardView(newCard, true); deckList.addCardView(newCard, card);
} }
} }
@ -150,7 +150,7 @@ public class DeckArea extends javax.swing.JPanel {
deckList.deselectAll(); deckList.deselectAll();
for (CardView card : cards) { for (CardView card : cards) {
CardView newCard = new CardView(card); CardView newCard = new CardView(card);
sideboardList.addCardView(newCard, true); sideboardList.addCardView(newCard, card);
} }
} }

View file

@ -533,10 +533,10 @@
<Component class="mage.client.deckeditor.DeckLegalityPanel" name="deckLegalityDisplay"> <Component class="mage.client.deckeditor.DeckLegalityPanel" name="deckLegalityDisplay">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[245, 155]"/> <Dimension value="[245, 255]"/>
</Property> </Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[85, 155]"/> <Dimension value="[85, 255]"/>
</Property> </Property>
<Property name="opaque" type="boolean" value="false"/> <Property name="opaque" type="boolean" value="false"/>
</Properties> </Properties>

View file

@ -121,12 +121,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
if (!SwingUtilities.isLeftMouseButton(e)) { if (!SwingUtilities.isLeftMouseButton(e)) {
return; return;
} }
List<String> cardNames = new ArrayList<>();
LegalityLabel label = (LegalityLabel) e.getComponent(); LegalityLabel label = (LegalityLabel) e.getComponent();
label.getValidator().getErrorsList().stream() List<String> cardNames = new ArrayList<>(label.selectCards());
.map(DeckValidatorError::getCardName)
.filter(Objects::nonNull)
.forEach(cardNames::add);
deckArea.getDeckList().deselectAll(); deckArea.getDeckList().deselectAll();
deckArea.getDeckList().selectByName(cardNames); deckArea.getDeckList().selectByName(cardNames);
deckArea.getSideboardList().deselectAll(); deckArea.getSideboardList().deselectAll();
@ -1290,8 +1286,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
panelInfo.setOpaque(false); panelInfo.setOpaque(false);
deckLegalityDisplay.setMaximumSize(new java.awt.Dimension(245, 155)); deckLegalityDisplay.setMaximumSize(new java.awt.Dimension(245, 255));
deckLegalityDisplay.setMinimumSize(new java.awt.Dimension(85, 155)); deckLegalityDisplay.setMinimumSize(new java.awt.Dimension(85, 255));
deckLegalityDisplay.setOpaque(false); deckLegalityDisplay.setOpaque(false);
deckLegalityDisplay.setVisible(false); deckLegalityDisplay.setVisible(false);
@ -1313,7 +1309,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
panelInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) panelInfoLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(panelInfoLayout.createSequentialGroup() .addGroup(panelInfoLayout.createSequentialGroup()
.addContainerGap() .addContainerGap()
.addComponent(deckLegalityDisplay, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(deckLegalityDisplay, javax.swing.GroupLayout.PREFERRED_SIZE, 255, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(bigCard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(bigCard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap()) .addContainerGap())

View file

@ -5,6 +5,8 @@ import mage.cards.decks.Deck;
import mage.cards.decks.DeckValidator; import mage.cards.decks.DeckValidator;
import mage.cards.mock.MockCard; import mage.cards.mock.MockCard;
import mage.cards.mock.MockSplitCard; import mage.cards.mock.MockSplitCard;
import mage.client.components.BracketLegalityLabel;
import mage.client.components.EdhPowerLevelLegalityLabel;
import mage.client.components.LegalityLabel; import mage.client.components.LegalityLabel;
import mage.deck.*; import mage.deck.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -15,7 +17,7 @@ import java.util.stream.Stream;
/** /**
* @author Elandril * @author Elandril, JayDi85
*/ */
public class DeckLegalityPanel extends javax.swing.JPanel { public class DeckLegalityPanel extends javax.swing.JPanel {
@ -101,6 +103,14 @@ public class DeckLegalityPanel extends javax.swing.JPanel {
new Frontier(), new HistoricalType2(), new PennyDreadfulCommander(), new EuropeanHighlander(), new CanadianHighlander() new Frontier(), new HistoricalType2(), new PennyDreadfulCommander(), new EuropeanHighlander(), new CanadianHighlander()
// not used: new Eternal(), new Momir(), new TinyLeaders() // not used: new Eternal(), new Momir(), new TinyLeaders()
).forEach(this::addLegalityLabel); ).forEach(this::addLegalityLabel);
// extra buttons like score
this.add(new EdhPowerLevelLegalityLabel());
// only 3 buttons allowed for one line
this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_1));
this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_2_3));
this.add(new BracketLegalityLabel(BracketLegalityLabel.BracketLevel.BRACKET_4_5));
addHidePanelButton(); addHidePanelButton();
revalidate(); revalidate();
@ -147,5 +157,4 @@ public class DeckLegalityPanel extends javax.swing.JPanel {
.map(LegalityLabel.class::cast) .map(LegalityLabel.class::cast)
.forEach(label -> label.validateDeck(deckToValidate)); .forEach(label -> label.validateDeck(deckToValidate));
} }
} }

View file

@ -2183,7 +2183,7 @@
<Component id="panelCardStyles" min="-2" max="-2" attributes="0"/> <Component id="panelCardStyles" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="panelCardImages" min="-2" max="-2" attributes="0"/> <Component id="panelCardImages" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="306" max="32767" attributes="0"/> <EmptySpace pref="309" max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -2224,7 +2224,7 @@
<Component id="cbPreferredImageLanguage" min="-2" pref="153" max="-2" attributes="0"/> <Component id="cbPreferredImageLanguage" min="-2" pref="153" max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
<EmptySpace min="0" pref="480" max="32767" attributes="0"/> <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
@ -2283,7 +2283,6 @@
</Component> </Component>
<Component class="javax.swing.JComboBox" name="cbPreferredImageLanguage"> <Component class="javax.swing.JComboBox" name="cbPreferredImageLanguage">
<Properties> <Properties>
<Property name="maximumRowCount" type="int" value="20"/>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor"> <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="4"> <StringArray count="4">
<StringItem index="0" value="Item 1"/> <StringItem index="0" value="Item 1"/>
@ -2318,15 +2317,48 @@
</Property> </Property>
</Properties> </Properties>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"> <Layout>
<Property name="axis" type="int" value="1"/> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cbCardRenderIconsForAbilities" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderIconsForPlayable" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator1" min="-2" pref="775" max="-2" attributes="0"/>
<Component id="cbCardRenderShowReminderText" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderHideSetSymbol" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderShowAbilityTextOverlay" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Component id="labelRenderMode" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cbCardRenderImageFallback" min="-2" pref="122" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="labelRenderMode" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cbCardRenderImageFallback" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderIconsForAbilities" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderIconsForPlayable" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="jSeparator1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderShowReminderText" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderHideSetSymbol" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="cbCardRenderShowAbilityTextOverlay" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout> </Layout>
<SubComponents> <SubComponents>
<Component class="javax.swing.JCheckBox" name="cbCardRenderImageFallback">
<Properties>
<Property name="text" type="java.lang.String" value="Render mode: MTGO style (off) or IMAGE style (on)"/>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="cbCardRenderIconsForAbilities"> <Component class="javax.swing.JCheckBox" name="cbCardRenderIconsForAbilities">
<Properties> <Properties>
<Property name="text" type="java.lang.String" value="Enable card icons for abilities (example: flying, deathtouch)"/> <Property name="text" type="java.lang.String" value="Enable card icons for abilities (example: flying, deathtouch)"/>
@ -2354,6 +2386,28 @@
<Property name="text" type="java.lang.String" value="Show ability text as overlay in big card view"/> <Property name="text" type="java.lang.String" value="Show ability text as overlay in big card view"/>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="labelRenderMode">
<Properties>
<Property name="text" type="java.lang.String" value="Render Mode:"/>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Image - Renders card image with text overlay&lt;br&gt; MTGO - Renders card frame around card art&lt;br&gt; Forced M15 - Renders all cards in the modern frame&lt;br&gt; Forced Retro - Renders all cards in the retro frame"/>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="cbCardRenderImageFallback">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="4">
<StringItem index="0" value="Item 1"/>
<StringItem index="1" value="Item 2"/>
<StringItem index="2" value="Item 3"/>
<StringItem index="3" value="Item 4"/>
</StringArray>
</Property>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Image - Renders card image with text overlay&lt;br&gt; MTGO - Renders card frame around card art&lt;br&gt; Forced M15 - Renders all cards in the MTGO style with the modern frame&lt;br&gt; Forced Retro - Renders all cards in the MTGO style with the retro frame"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents> </SubComponents>
</Container> </Container>
</SubComponents> </SubComponents>

View file

@ -4,10 +4,7 @@ import mage.client.MageFrame;
import mage.client.SessionHandler; import mage.client.SessionHandler;
import mage.client.components.KeyBindButton; import mage.client.components.KeyBindButton;
import mage.client.themes.ThemeType; import mage.client.themes.ThemeType;
import mage.client.util.CardLanguage; import mage.client.util.*;
import mage.client.util.ClientDefaultSettings;
import mage.client.util.GUISizeHelper;
import mage.client.util.ImageHelper;
import mage.client.util.audio.MusicPlayer; import mage.client.util.audio.MusicPlayer;
import mage.client.util.gui.BufferedImageBuilder; import mage.client.util.gui.BufferedImageBuilder;
import mage.client.util.gui.GuiDisplayUtil; import mage.client.util.gui.GuiDisplayUtil;
@ -37,7 +34,6 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.prefs.BackingStoreException; import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD; import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD;
import static mage.constants.Constants.*; import static mage.constants.Constants.*;
@ -89,8 +85,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip"; public static final String KEY_CARD_IMAGES_SAVE_TO_ZIP = "cardImagesSaveToZip";
public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage"; public static final String KEY_CARD_IMAGES_PREF_LANGUAGE = "cardImagesPreferredImageLaguage";
public static final String KEY_CARD_RENDERING_IMAGE_MODE = "cardRenderingFallback"; public static final String KEY_CARD_RENDERING_IMAGE_MODE = "cardRenderingMode";
public static final String KEY_CARD_RENDERING_ENABLE_RETRO_FRAMES = "cardRenderingRetroFrames";
public static final String KEY_CARD_RENDERING_ICONS_FOR_ABILITIES = "cardRenderingIconsForAbilities"; public static final String KEY_CARD_RENDERING_ICONS_FOR_ABILITIES = "cardRenderingIconsForAbilities";
public static final String KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE = "cardRenderingIconsForPlayable"; public static final String KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE = "cardRenderingIconsForPlayable";
public static final String KEY_CARD_RENDERING_REMINDER_TEXT = "cardRenderingReminderText"; public static final String KEY_CARD_RENDERING_REMINDER_TEXT = "cardRenderingReminderText";
@ -268,12 +263,14 @@ public class PreferencesDialog extends javax.swing.JDialog {
public static final String KEY_AUTO_TARGET_LEVEL = "autoTargetLevel"; public static final String KEY_AUTO_TARGET_LEVEL = "autoTargetLevel";
// pref setting for deck generator // pref setting for deck generator
public static final String KEY_NEW_DECK_GENERATOR_COLORS = "newDeckGeneratorDeckColors";
public static final String KEY_NEW_DECK_GENERATOR_DECK_SIZE = "newDeckGeneratorDeckSize"; public static final String KEY_NEW_DECK_GENERATOR_DECK_SIZE = "newDeckGeneratorDeckSize";
public static final String KEY_NEW_DECK_GENERATOR_SET = "newDeckGeneratorSet"; public static final String KEY_NEW_DECK_GENERATOR_SET = "newDeckGeneratorSet";
public static final String KEY_NEW_DECK_GENERATOR_SINGLETON = "newDeckGeneratorSingleton"; public static final String KEY_NEW_DECK_GENERATOR_SINGLETON = "newDeckGeneratorSingleton";
public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts"; public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts";
public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands"; public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands";
public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless"; public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless";
public static final String KEY_NEW_DECK_GENERATOR_COMMANDER = "newDeckGeneratorCommander";
public static final String KEY_NEW_DECK_GENERATOR_ADVANCED = "newDeckGeneratorAdvanced"; public static final String KEY_NEW_DECK_GENERATOR_ADVANCED = "newDeckGeneratorAdvanced";
public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage"; public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage";
public static final String KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE = "newDeckGeneratorNonCreaturePercentage"; public static final String KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE = "newDeckGeneratorNonCreaturePercentage";
@ -761,6 +758,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
} }
cbPreferredImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList())); cbPreferredImageLanguage.setModel(new DefaultComboBoxModel<>(CardLanguage.toList()));
cbCardRenderImageFallback.setModel(new DefaultComboBoxModel<>(CardRenderMode.toList()));
} }
private void createSizeSetting(Integer position, String key, Integer defaultValue, boolean useExample, String name, String hint) { private void createSizeSetting(Integer position, String key, Integer defaultValue, boolean useExample, String name, String hint) {
@ -962,14 +960,14 @@ public class PreferencesDialog extends javax.swing.JDialog {
cbPreferredImageLanguage = new javax.swing.JComboBox<>(); cbPreferredImageLanguage = new javax.swing.JComboBox<>();
labelPreferredImageLanguage = new javax.swing.JLabel(); labelPreferredImageLanguage = new javax.swing.JLabel();
panelCardStyles = new javax.swing.JPanel(); panelCardStyles = new javax.swing.JPanel();
cbCardRenderImageFallback = new javax.swing.JCheckBox();
cbCardRenderRetroFrames = new javax.swing.JCheckBox();
cbCardRenderIconsForAbilities = new javax.swing.JCheckBox(); cbCardRenderIconsForAbilities = new javax.swing.JCheckBox();
cbCardRenderIconsForPlayable = new javax.swing.JCheckBox(); cbCardRenderIconsForPlayable = new javax.swing.JCheckBox();
jSeparator1 = new javax.swing.JSeparator(); jSeparator1 = new javax.swing.JSeparator();
cbCardRenderShowReminderText = new javax.swing.JCheckBox(); cbCardRenderShowReminderText = new javax.swing.JCheckBox();
cbCardRenderHideSetSymbol = new javax.swing.JCheckBox(); cbCardRenderHideSetSymbol = new javax.swing.JCheckBox();
cbCardRenderShowAbilityTextOverlay = new javax.swing.JCheckBox(); cbCardRenderShowAbilityTextOverlay = new javax.swing.JCheckBox();
labelRenderMode = new javax.swing.JLabel();
cbCardRenderImageFallback = new javax.swing.JComboBox<>();
tabPhases = new javax.swing.JPanel(); tabPhases = new javax.swing.JPanel();
jLabelHeadLine = new javax.swing.JLabel(); jLabelHeadLine = new javax.swing.JLabel();
jLabelYourTurn = new javax.swing.JLabel(); jLabelYourTurn = new javax.swing.JLabel();
@ -2279,7 +2277,6 @@ public class PreferencesDialog extends javax.swing.JDialog {
} }
}); });
cbPreferredImageLanguage.setMaximumRowCount(20);
cbPreferredImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); cbPreferredImageLanguage.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
labelPreferredImageLanguage.setText("Default images language:"); labelPreferredImageLanguage.setText("Default images language:");
@ -2305,7 +2302,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(labelPreferredImageLanguage) .add(labelPreferredImageLanguage)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) .add(cbPreferredImageLanguage, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 153, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
.add(0, 480, Short.MAX_VALUE))) .add(0, 0, Short.MAX_VALUE)))
.addContainerGap()) .addContainerGap())
); );
panelCardImagesLayout.setVerticalGroup( panelCardImagesLayout.setVerticalGroup(
@ -2325,29 +2322,59 @@ public class PreferencesDialog extends javax.swing.JDialog {
); );
panelCardStyles.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)")); panelCardStyles.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), "Card styles (restart xmage to apply new settings)"));
panelCardStyles.setLayout(new javax.swing.BoxLayout(panelCardStyles, javax.swing.BoxLayout.Y_AXIS));
cbCardRenderImageFallback.setText("Render mode: MTGO style (off) or IMAGE style (on)");
panelCardStyles.add(cbCardRenderImageFallback);
cbCardRenderRetroFrames.setText("Force retro frames (MTGO render mode will use old border for all cards)");
panelCardStyles.add(cbCardRenderRetroFrames);
cbCardRenderIconsForAbilities.setText("Enable card icons for abilities (example: flying, deathtouch)"); cbCardRenderIconsForAbilities.setText("Enable card icons for abilities (example: flying, deathtouch)");
panelCardStyles.add(cbCardRenderIconsForAbilities);
cbCardRenderIconsForPlayable.setText("Enable card icons for playable abilities (example: if you can activate card's ability then show a special icon in the corner)"); cbCardRenderIconsForPlayable.setText("Enable card icons for playable abilities (example: if you can activate card's ability then show a special icon in the corner)");
panelCardStyles.add(cbCardRenderIconsForPlayable);
panelCardStyles.add(jSeparator1);
cbCardRenderShowReminderText.setText("Show reminder text in rendered card textboxes"); cbCardRenderShowReminderText.setText("Show reminder text in rendered card textboxes");
panelCardStyles.add(cbCardRenderShowReminderText);
cbCardRenderHideSetSymbol.setText("Hide set symbols on cards (more space on the type line for card types)"); cbCardRenderHideSetSymbol.setText("Hide set symbols on cards (more space on the type line for card types)");
panelCardStyles.add(cbCardRenderHideSetSymbol);
cbCardRenderShowAbilityTextOverlay.setText("Show ability text as overlay in big card view"); cbCardRenderShowAbilityTextOverlay.setText("Show ability text as overlay in big card view");
panelCardStyles.add(cbCardRenderShowAbilityTextOverlay);
labelRenderMode.setText("Render Mode:");
labelRenderMode.setToolTipText("<HTML>Image - Renders card image with text overlay<br> MTGO - Renders card frame around card art<br> Forced M15 - Renders all cards in the modern frame<br> Forced Retro - Renders all cards in the retro frame");
cbCardRenderImageFallback.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
cbCardRenderImageFallback.setToolTipText("<HTML>Image - Renders card image with text overlay<br> MTGO - Renders card frame around card art<br> Forced M15 - Renders all cards in the MTGO style with the modern frame<br> Forced Retro - Renders all cards in the MTGO style with the retro frame");
org.jdesktop.layout.GroupLayout panelCardStylesLayout = new org.jdesktop.layout.GroupLayout(panelCardStyles);
panelCardStyles.setLayout(panelCardStylesLayout);
panelCardStylesLayout.setHorizontalGroup(
panelCardStylesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(cbCardRenderIconsForAbilities)
.add(cbCardRenderIconsForPlayable)
.add(jSeparator1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 775, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(cbCardRenderShowReminderText)
.add(cbCardRenderHideSetSymbol)
.add(cbCardRenderShowAbilityTextOverlay)
.add(panelCardStylesLayout.createSequentialGroup()
.add(6, 6, 6)
.add(labelRenderMode)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(cbCardRenderImageFallback, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 122, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
);
panelCardStylesLayout.setVerticalGroup(
panelCardStylesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(panelCardStylesLayout.createSequentialGroup()
.add(0, 0, 0)
.add(panelCardStylesLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(labelRenderMode)
.add(cbCardRenderImageFallback, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
.add(0, 0, 0)
.add(cbCardRenderIconsForAbilities)
.add(0, 0, 0)
.add(cbCardRenderIconsForPlayable)
.add(0, 0, 0)
.add(jSeparator1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.add(0, 0, 0)
.add(cbCardRenderShowReminderText)
.add(0, 0, 0)
.add(cbCardRenderHideSetSymbol)
.add(0, 0, 0)
.add(cbCardRenderShowAbilityTextOverlay))
);
org.jdesktop.layout.GroupLayout tabGuiImagesLayout = new org.jdesktop.layout.GroupLayout(tabGuiImages); org.jdesktop.layout.GroupLayout tabGuiImagesLayout = new org.jdesktop.layout.GroupLayout(tabGuiImages);
tabGuiImages.setLayout(tabGuiImagesLayout); tabGuiImages.setLayout(tabGuiImagesLayout);
@ -2367,7 +2394,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
.add(panelCardStyles, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(panelCardStyles, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(panelCardImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .add(panelCardImages, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
.addContainerGap(306, Short.MAX_VALUE)) .addContainerGap(309, Short.MAX_VALUE))
); );
tabsPanel.addTab("GUI Images", tabGuiImages); tabsPanel.addTab("GUI Images", tabGuiImages);
@ -3038,8 +3065,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
save(prefs, dialog.cbUseRandomBattleImage, KEY_BATTLEFIELD_IMAGE_RANDOM, "true", "false"); save(prefs, dialog.cbUseRandomBattleImage, KEY_BATTLEFIELD_IMAGE_RANDOM, "true", "false");
// rendering // rendering
save(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_IMAGE_MODE, "true", "false"); save(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_IMAGE_MODE);
save(prefs, dialog.cbCardRenderRetroFrames, KEY_CARD_RENDERING_ENABLE_RETRO_FRAMES, "true", "false");
save(prefs, dialog.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "false"); save(prefs, dialog.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "false");
save(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "false"); save(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "false");
save(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true", "false"); save(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true", "false");
@ -3490,8 +3516,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode())); dialog.cbPreferredImageLanguage.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_IMAGES_PREF_LANGUAGE, CardLanguage.ENGLISH.getCode()));
// rendering settings // rendering settings
load(prefs, dialog.cbCardRenderImageFallback, KEY_CARD_RENDERING_IMAGE_MODE, "true", "false"); dialog.cbCardRenderImageFallback.setSelectedItem(MageFrame.getPreferences().get(KEY_CARD_RENDERING_IMAGE_MODE, CardRenderMode.MTGO.toString()));
load(prefs, dialog.cbCardRenderRetroFrames, KEY_CARD_RENDERING_ENABLE_RETRO_FRAMES, "true", "false");
load(prefs, dialog.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "true"); load(prefs, dialog.cbCardRenderIconsForAbilities, KEY_CARD_RENDERING_ICONS_FOR_ABILITIES, "true", "true");
load(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "true"); load(prefs, dialog.cbCardRenderIconsForPlayable, KEY_CARD_RENDERING_ICONS_FOR_PLAYABLE, "true", "true");
load(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true"); load(prefs, dialog.cbCardRenderHideSetSymbol, KEY_CARD_RENDERING_SET_SYMBOL, "true");
@ -3815,15 +3840,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
} }
public static int getRenderMode() { public static int getRenderMode() {
if (getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_IMAGE_MODE, "false").equals("false")) { return CardRenderMode.fromString(getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_IMAGE_MODE, CardRenderMode.MTGO.toString())).getId();
return 0; // mtgo
} else {
return 1; // image
}
}
public static boolean getRenderRetroFrames() {
return (getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_ENABLE_RETRO_FRAMES, "true").endsWith("true"));
} }
public static boolean getRenderIconsForAbilities() { public static boolean getRenderIconsForAbilities() {
@ -4053,8 +4070,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JCheckBox cbCardRenderHideSetSymbol; private javax.swing.JCheckBox cbCardRenderHideSetSymbol;
private javax.swing.JCheckBox cbCardRenderIconsForAbilities; private javax.swing.JCheckBox cbCardRenderIconsForAbilities;
private javax.swing.JCheckBox cbCardRenderIconsForPlayable; private javax.swing.JCheckBox cbCardRenderIconsForPlayable;
private javax.swing.JCheckBox cbCardRenderImageFallback; private javax.swing.JComboBox<String> cbCardRenderImageFallback;
private javax.swing.JCheckBox cbCardRenderRetroFrames;
private javax.swing.JCheckBox cbCardRenderShowAbilityTextOverlay; private javax.swing.JCheckBox cbCardRenderShowAbilityTextOverlay;
private javax.swing.JCheckBox cbCardRenderShowReminderText; private javax.swing.JCheckBox cbCardRenderShowReminderText;
private javax.swing.JCheckBox cbConfirmEmptyManaPool; private javax.swing.JCheckBox cbConfirmEmptyManaPool;
@ -4186,6 +4202,7 @@ public class PreferencesDialog extends javax.swing.JDialog {
private javax.swing.JLabel labelNextTurn; private javax.swing.JLabel labelNextTurn;
private javax.swing.JLabel labelPreferredImageLanguage; private javax.swing.JLabel labelPreferredImageLanguage;
private javax.swing.JLabel labelPriorEnd; private javax.swing.JLabel labelPriorEnd;
private javax.swing.JLabel labelRenderMode;
private javax.swing.JLabel labelSizeGroup1; private javax.swing.JLabel labelSizeGroup1;
private javax.swing.JLabel labelSizeGroup2; private javax.swing.JLabel labelSizeGroup2;
private javax.swing.JLabel labelSkipStep; private javax.swing.JLabel labelSkipStep;

View file

@ -116,11 +116,9 @@
<Component class="javax.swing.JComboBox" name="comboRenderMode"> <Component class="javax.swing.JComboBox" name="comboRenderMode">
<Properties> <Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor"> <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="2"> <StringArray count="0"/>
<StringItem index="0" value="MTGO"/>
<StringItem index="1" value="Image"/>
</StringArray>
</Property> </Property>
<Property name="toolTipText" type="java.lang.String" value="&lt;HTML&gt;Image - Renders card image with text overlay&lt;br&gt; MTGO - Renders card frame around card art&lt;br&gt; Forced M15 - Renders all cards in the MTGO style with the modern frame&lt;br&gt; Forced Retro - Renders all cards in the MTGO style with the retro frame"/>
</Properties> </Properties>
<Events> <Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboRenderModeItemStateChanged"/> <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="comboRenderModeItemStateChanged"/>

View file

@ -16,7 +16,7 @@ import mage.client.cards.BigCard;
import mage.client.game.PlayAreaPanel; import mage.client.game.PlayAreaPanel;
import mage.client.game.PlayerPanelExt; import mage.client.game.PlayerPanelExt;
import mage.client.themes.ThemeType; import mage.client.themes.ThemeType;
import mage.client.util.ClientEventType; import mage.client.util.*;
import mage.client.util.Event; import mage.client.util.Event;
import mage.client.util.GUISizeHelper; import mage.client.util.GUISizeHelper;
import mage.client.util.Listener; import mage.client.util.Listener;
@ -82,6 +82,7 @@ public class TestCardRenderDialog extends MageDialog {
getRootPane().setDefaultButton(buttonCancel); getRootPane().setDefaultButton(buttonCancel);
// init render mode // init render mode
this.comboRenderMode.setModel(new DefaultComboBoxModel<>(CardRenderMode.toList()));
this.comboRenderMode.setSelectedIndex(PreferencesDialog.getRenderMode()); this.comboRenderMode.setSelectedIndex(PreferencesDialog.getRenderMode());
// init themes list // init themes list
@ -431,6 +432,7 @@ public class TestCardRenderDialog extends MageDialog {
cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "185", 0, 0, 0, true, false, null)); // Judith, the Scourge Diva cardViews.add(createPermanentCard(game, playerYou.getId(), "RNA", "185", 0, 0, 0, true, false, null)); // Judith, the Scourge Diva
cardViews.add(createHandCard(game, playerYou.getId(), "DIS", "153")); // Odds // Ends (split card) cardViews.add(createHandCard(game, playerYou.getId(), "DIS", "153")); // Odds // Ends (split card)
cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "38")); // Animating Faerie (adventure card) cardViews.add(createHandCard(game, playerYou.getId(), "ELD", "38")); // Animating Faerie (adventure card)
cardViews.add(createHandCard(game, playerYou.getId(), "LEA", "278")); // Bayou (retro frame)
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, false, false)); // face down cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, false, false)); // face down
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", true, false, true)); // morphed cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", true, false, true)); // morphed
cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, true, false)); // manifested cardViews.add(createFaceDownCard(game, playerOpponent.getId(), "ELD", "38", false, true, false)); // manifested
@ -635,7 +637,7 @@ public class TestCardRenderDialog extends MageDialog {
labelRenderMode.setText("Render mode:"); labelRenderMode.setText("Render mode:");
comboRenderMode.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "MTGO", "Image" })); comboRenderMode.setToolTipText("<HTML>Image - Renders card image with text overlay<br> MTGO - Renders card frame around card art<br> Forced M15 - Renders all cards in the MTGO style with the modern frame<br> Forced Retro - Renders all cards in the MTGO style with the retro frame");
comboRenderMode.addItemListener(new java.awt.event.ItemListener() { comboRenderMode.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) { public void itemStateChanged(java.awt.event.ItemEvent evt) {
comboRenderModeItemStateChanged(evt); comboRenderModeItemStateChanged(evt);

View file

@ -0,0 +1,59 @@
package mage.client.util;
import java.util.ArrayList;
import java.util.List;
public enum CardRenderMode {
MTGO("MTGO", 0),
IMAGE("Image", 1),
FORCED_M15("Forced M15", 2),
FORCED_RETRO("Forced Retro", 3);
private final String text;
private final int id;
CardRenderMode(String text, int id) {
this.text = text;
this.id = id;
}
@Override
public String toString() {
return text;
}
public String getText() {
return text;
}
public int getId() {
return id;
}
public static String[] toList() {
List<String> list = new ArrayList<>();
for (CardRenderMode mode : CardRenderMode.values()) {
list.add(mode.toString());
}
return list.toArray(new String[0]);
}
public static CardRenderMode fromId(int id) {
for (CardRenderMode mode : CardRenderMode.values()) {
if (mode.getId() == id) {
return mode;
}
}
return MTGO;
}
public static CardRenderMode fromString(String text) {
for (CardRenderMode mode : CardRenderMode.values()) {
if (mode.text.equals(text)) {
return mode;
}
}
return MTGO;
}
}

View file

@ -19,6 +19,7 @@ public class CardViewEDHPowerLevelComparator implements CardViewComparator {
return "EDH: " + getPowerLevel(sample); return "EDH: " + getPowerLevel(sample);
} }
// TODO: it's outdated code, must migrate to shared code from AbstractCommander
private int getPowerLevel(CardView card) { private int getPowerLevel(CardView card) {
int thisMaxPower = 0; int thisMaxPower = 0;

View file

@ -50,6 +50,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
private CardRenderer cardRenderer; private CardRenderer cardRenderer;
private int updateArtImageStamp; private int updateArtImageStamp;
private final int cardRenderMode;
private static class ImageKey { private static class ImageKey {
final BufferedImage artImage; final BufferedImage artImage;
@ -84,7 +85,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
sb.append((char) (this.view.isCanAttack() ? 1 : 0)); sb.append((char) (this.view.isCanAttack() ? 1 : 0));
sb.append((char) (this.view.isCanBlock() ? 1 : 0)); sb.append((char) (this.view.isCanBlock() ? 1 : 0));
sb.append((char) (this.view.isFaceDown() ? 1 : 0)); sb.append((char) (this.view.isFaceDown() ? 1 : 0));
sb.append((char) this.view.getFrameStyle().ordinal()); sb.append((char) (this.view.getFrameStyle() != null ? this.view.getFrameStyle().ordinal() : -1));
if (this.view instanceof PermanentView) { if (this.view instanceof PermanentView) {
sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0)); sb.append((char) (((PermanentView) this.view).hasSummoningSickness() ? 1 : 0));
sb.append((char) (((PermanentView) this.view).getDamage())); sb.append((char) (((PermanentView) this.view).getDamage()));
@ -143,12 +144,13 @@ public class CardPanelRenderModeMTGO extends CardPanel {
} }
public CardPanelRenderModeMTGO(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, public CardPanelRenderModeMTGO(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback,
final boolean foil, Dimension dimension, boolean needFullPermanentRender) { final boolean foil, Dimension dimension, boolean needFullPermanentRender, int renderMode) {
// Call to super // Call to super
super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender); super(newGameCard, gameId, loadImage, callback, foil, dimension, needFullPermanentRender);
// Renderer // Renderer
cardRenderer = cardRendererFactory.create(getGameCard()); cardRenderMode = renderMode;
cardRenderer = cardRendererFactory.create(getGameCard(), cardRenderMode);
// Draw the parts // Draw the parts
initialDraw(); initialDraw();
@ -265,7 +267,7 @@ public class CardPanelRenderModeMTGO extends CardPanel {
// Update renderer // Update renderer
cardImage = null; cardImage = null;
cardRenderer = cardRendererFactory.create(getGameCard()); cardRenderer = cardRendererFactory.create(getGameCard(), cardRenderMode);
cardRenderer.setArtImage(artImage); cardRenderer.setArtImage(artImage);
// Repaint // Repaint

View file

@ -2,6 +2,7 @@ package org.mage.card.arcane;
import mage.cards.FrameStyle; import mage.cards.FrameStyle;
import mage.client.dialog.PreferencesDialog; import mage.client.dialog.PreferencesDialog;
import mage.client.util.CardRenderMode;
import mage.view.CardView; import mage.view.CardView;
/** /**
@ -13,12 +14,27 @@ public class CardRendererFactory {
} }
public CardRenderer create(CardView card) { public CardRenderer create(CardView card) {
return create(card, -1);
}
public CardRenderer create(CardView card, int renderModeOverride) {
if (card.isSplitCard()) { if (card.isSplitCard()) {
return new ModernSplitCardRenderer(card); return new ModernSplitCardRenderer(card);
} else if (card.getFrameStyle().equals(FrameStyle.RETRO) || card.getFrameStyle().equals(FrameStyle.LEA_ORIGINAL_DUAL_LAND_ART_BASIC) || PreferencesDialog.getRenderRetroFrames()) { } else if (shouldRenderRetro(card, renderModeOverride)) {
// TODO: implement split card renderer for retro cards
return new RetroCardRenderer(card); return new RetroCardRenderer(card);
} else { } else {
return new ModernCardRenderer(card); return new ModernCardRenderer(card);
} }
} }
private static boolean shouldRenderRetro(CardView card, int renderModeOverride) {
int renderMode = PreferencesDialog.getRenderMode();
if (renderModeOverride != -1) {
renderMode = renderModeOverride;
}
boolean renderMTGO = (card.getFrameStyle().equals(FrameStyle.RETRO) || card.getFrameStyle().equals(FrameStyle.LEA_ORIGINAL_DUAL_LAND_ART_BASIC)) && renderMode == CardRenderMode.MTGO.ordinal();
boolean forcedRetro = renderMode == CardRenderMode.FORCED_RETRO.ordinal();
return renderMTGO || forcedRetro;
}
} }

View file

@ -3,7 +3,7 @@ package org.mage.plugins.card;
import mage.cards.MageCard; import mage.cards.MageCard;
import mage.cards.MagePermanent; import mage.cards.MagePermanent;
import mage.cards.action.ActionCallback; import mage.cards.action.ActionCallback;
import mage.client.util.GUISizeHelper; import mage.client.util.*;
import mage.interfaces.plugin.CardPlugin; import mage.interfaces.plugin.CardPlugin;
import mage.view.CardView; import mage.view.CardView;
import mage.view.CounterView; import mage.view.CounterView;
@ -35,6 +35,8 @@ import java.util.List;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static mage.client.util.CardRenderMode.*;
/** /**
* {@link CardPlugin} implementation. * {@link CardPlugin} implementation.
* *
@ -102,16 +104,19 @@ public class CardPluginImpl implements CardPlugin {
* yet, so use old component based rendering for the split cards. * yet, so use old component based rendering for the split cards.
*/ */
private CardPanel makeCardPanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback, private CardPanel makeCardPanel(CardView view, UUID gameId, boolean loadImage, ActionCallback callback,
boolean isFoil, Dimension dimension, int renderMode, boolean needFullPermanentRender) { boolean isFoil, Dimension dimension, int renderModeId, boolean needFullPermanentRender) {
switch (renderMode) { CardRenderMode cardRenderMode = CardRenderMode.fromId(renderModeId);
case 0: switch (cardRenderMode) {
case MTGO:
case FORCED_M15:
case FORCED_RETRO:
return new CardPanelRenderModeMTGO(view, gameId, loadImage, callback, isFoil, dimension, return new CardPanelRenderModeMTGO(view, gameId, loadImage, callback, isFoil, dimension,
needFullPermanentRender); needFullPermanentRender, renderModeId);
case 1: case IMAGE:
return new CardPanelRenderModeImage(view, gameId, loadImage, callback, isFoil, dimension, return new CardPanelRenderModeImage(view, gameId, loadImage, callback, isFoil, dimension,
needFullPermanentRender); needFullPermanentRender);
default: default:
throw new IllegalStateException("Unknown render mode " + renderMode); throw new IllegalStateException("Unknown render mode " + cardRenderMode);
} }
} }

View file

@ -19,7 +19,7 @@ import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* @author TheElk801 * @author TheElk801, JayDi85
*/ */
public abstract class AbstractCommander extends Constructed { public abstract class AbstractCommander extends Constructed {
@ -88,13 +88,13 @@ public abstract class AbstractCommander extends Constructed {
boolean valid = true; boolean valid = true;
for (Card card : deck.getCards()) { for (Card card : deck.getCards()) {
if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) {
addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (need " + colorIdentity + ", but get " + card.getColorIdentity() + ")", true);
valid = false; valid = false;
} }
} }
for (Card card : deck.getSideboard()) { for (Card card : deck.getSideboard()) {
if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) { if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) {
addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (need " + colorIdentity + ", but get " + card.getColorIdentity() + ")", true);
valid = false; valid = false;
} }
} }
@ -258,14 +258,18 @@ public abstract class AbstractCommander extends Constructed {
} }
@Override @Override
public int getEdhPowerLevel(Deck deck) { public int getEdhPowerLevel(Deck deck, List<String> foundPowerCards, List<String> foundInfo) {
if (deck == null) { // calculate power level and find all related cards with it to show in hints (see deck validation in deck editor)
return 0; // example: https://edhpowerlevel.com
}
int edhPowerLevel = 0; int edhPowerLevel = 0;
int commanderColors = 0; int commanderColors = 0;
int numberInfinitePieces = 0; int numberInfinitePieces = 0;
foundPowerCards.clear();
foundInfo.clear();
if (deck == null) {
return 0;
}
for (Card card : deck.getCards()) { for (Card card : deck.getCards()) {
@ -330,6 +334,7 @@ public abstract class AbstractCommander extends Constructed {
boolean yourOpponentsControl = false; boolean yourOpponentsControl = false;
boolean whenYouCast = false; boolean whenYouCast = false;
List<String> cardStates = new ArrayList<>();
for (String str : card.getRules()) { for (String str : card.getRules()) {
String s = str.toLowerCase(Locale.ENGLISH); String s = str.toLowerCase(Locale.ENGLISH);
annihilator |= s.contains("annihilator"); annihilator |= s.contains("annihilator");
@ -406,178 +411,236 @@ public abstract class AbstractCommander extends Constructed {
if (extraTurns) { if (extraTurns) {
thisMaxPower = Math.max(thisMaxPower, 7); thisMaxPower = Math.max(thisMaxPower, 7);
cardStates.add(String.format("extraTurns %d", 7));
} }
if (buyback) { if (buyback) {
thisMaxPower = Math.max(thisMaxPower, 6); thisMaxPower = Math.max(thisMaxPower, 6);
cardStates.add(String.format("buyback %d", 6));
} }
if (tutor) { if (tutor) {
thisMaxPower = Math.max(thisMaxPower, 6); thisMaxPower = Math.max(thisMaxPower, 6);
cardStates.add(String.format("tutor %d", 6));
} }
if (annihilator) { if (annihilator) {
thisMaxPower = Math.max(thisMaxPower, 5); thisMaxPower = Math.max(thisMaxPower, 5);
cardStates.add(String.format("annihilator %d", 5));
} }
if (cantUntap) { if (cantUntap) {
thisMaxPower = Math.max(thisMaxPower, 5); thisMaxPower = Math.max(thisMaxPower, 5);
cardStates.add(String.format("cantUntap %d", 5));
} }
if (costLessEach) { if (costLessEach) {
thisMaxPower = Math.max(thisMaxPower, 5); thisMaxPower = Math.max(thisMaxPower, 5);
cardStates.add(String.format("costLessEach %d", 5));
} }
if (infect) { if (infect) {
thisMaxPower = Math.max(thisMaxPower, 5); thisMaxPower = Math.max(thisMaxPower, 5);
cardStates.add(String.format("infect %d", 5));
} }
if (overload) { if (overload) {
thisMaxPower = Math.max(thisMaxPower, 5); thisMaxPower = Math.max(thisMaxPower, 5);
cardStates.add(String.format("overload %d", 5));
} }
if (twiceAs) { if (twiceAs) {
thisMaxPower = Math.max(thisMaxPower, 5); thisMaxPower = Math.max(thisMaxPower, 5);
cardStates.add(String.format("twiceAs %d", 5));
} }
if (cascade) { if (cascade) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("cascade %d", 4));
} }
if (doesntUntap) { if (doesntUntap) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("doesntUntap %d", 4));
} }
if (each) { if (each) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("each %d", 4));
} }
if (exileAll) { if (exileAll) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("exileAll %d", 4));
} }
if (flash) { if (flash) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("flash %d", 4));
} }
if (flashback) { if (flashback) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("flashback %d", 4));
} }
if (flicker) { if (flicker) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("flicker %d", 4));
} }
if (gainControl) { if (gainControl) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("gainControl %d", 4));
} }
if (lifeTotalBecomes) { if (lifeTotalBecomes) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("lifeTotalBecomes %d", 4));
} }
if (mayCastForFree) { if (mayCastForFree) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("mayCastForFree %d", 4));
} }
if (preventDamage) { if (preventDamage) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("preventDamage %d", 4));
} }
if (proliferate) { if (proliferate) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("proliferate %d", 4));
} }
if (protection) { if (protection) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("protection %d", 4));
} }
if (putUnderYourControl) { if (putUnderYourControl) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("putUnderYourControl %d", 4));
} }
if (returnFromYourGY) { if (returnFromYourGY) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("returnFromYourGY %d", 4));
} }
if (sacrifice) { if (sacrifice) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("sacrifice %d", 2));
} }
if (skip) { if (skip) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("skip %d", 4));
} }
if (storm) { if (storm) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("storm %d", 4));
} }
if (unblockable) { if (unblockable) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("unblockable %d", 4));
} }
if (whenCounterThatSpell) { if (whenCounterThatSpell) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("whenCounterThatSpell %d", 4));
} }
if (wheneverEnters) { if (wheneverEnters) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("wheneverEnters %d", 4));
} }
if (xCost) { if (xCost) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("xCost %d", 4));
} }
if (youControlTarget) { if (youControlTarget) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("youControlTarget %d", 4));
} }
if (yourOpponentsControl) { if (yourOpponentsControl) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("yourOpponentsControl %d", 4));
} }
if (whenYouCast) { if (whenYouCast) {
thisMaxPower = Math.max(thisMaxPower, 4); thisMaxPower = Math.max(thisMaxPower, 4);
cardStates.add(String.format("whenYouCast %d", 4));
} }
if (anyNumberOfTarget) { if (anyNumberOfTarget) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("anyNumberOfTarget %d", 4));
} }
if (createToken) { if (createToken) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("createToken %d", 3));
} }
if (destroyAll) { if (destroyAll) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("destroyAll %d", 3));
} }
if (dredge) { if (dredge) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("dredge %d", 3));
} }
if (hexproof) { if (hexproof) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("hexproof %d", 3));
} }
if (shroud) { if (shroud) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("shroud %d", 3));
} }
if (undying) { if (undying) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("undying %d", 3));
} }
if (persist) { if (persist) {
thisMaxPower = Math.max(thisMaxPower, 3); thisMaxPower = Math.max(thisMaxPower, 3);
cardStates.add(String.format("persist %d", 3));
} }
if (cantBe) { if (cantBe) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("cantBe %d", 2));
} }
if (evoke) { if (evoke) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("evoke %d", 2));
} }
if (exile) { if (exile) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("exile %d", 2));
} }
if (menace) { if (menace) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("menace %d", 2));
} }
if (miracle) { if (miracle) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("miracle %d", 2));
} }
if (sliver) { if (sliver) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("sliver %d", 2));
} }
if (untapTarget) { if (untapTarget) {
thisMaxPower = Math.max(thisMaxPower, 2); thisMaxPower = Math.max(thisMaxPower, 2);
cardStates.add(String.format("untapTarget %d", 2));
} }
if (copy) { if (copy) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("copy %d", 1));
} }
if (counter) { if (counter) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("counter %d", 1));
} }
if (destroy) { if (destroy) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("destroy %d", 1));
} }
if (drawCards) { if (drawCards) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("drawCards %d", 1));
} }
if (exalted) { if (exalted) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("exalted %d", 1));
} }
if (retrace) { if (retrace) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("retrace %d", 1));
} }
if (trample) { if (trample) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("trample %d", 1));
} }
if (tutorBasic) { if (tutorBasic) {
thisMaxPower = Math.max(thisMaxPower, 1); thisMaxPower = Math.max(thisMaxPower, 1);
cardStates.add(String.format("tutorBasic %d", 1));
} }
if (card.isPlaneswalker()) { if (card.isPlaneswalker()) {
thisMaxPower = Math.max(thisMaxPower, 6); thisMaxPower = Math.max(thisMaxPower, 6);
cardStates.add(String.format("isPlaneswalker %d", 6));
} }
String cn = card.getName().toLowerCase(Locale.ENGLISH); String cn = card.getName().toLowerCase(Locale.ENGLISH);
@ -845,6 +908,7 @@ public abstract class AbstractCommander extends Constructed {
|| cn.equals("zealous conscripts") || cn.equals("zealous conscripts")
|| cn.equals("zur the enchanter")) { || cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 12); thisMaxPower = Math.max(thisMaxPower, 12);
cardStates.add(String.format("saltiest card %d", 12));
} }
// Parts of infinite combos // Parts of infinite combos
@ -906,6 +970,7 @@ public abstract class AbstractCommander extends Constructed {
|| cn.equals("worthy cause") || cn.equals("yawgmoth's will") || cn.equals("worthy cause") || cn.equals("yawgmoth's will")
|| cn.equals("zealous conscripts")) { || cn.equals("zealous conscripts")) {
thisMaxPower = Math.max(thisMaxPower, 15); thisMaxPower = Math.max(thisMaxPower, 15);
cardStates.add(String.format("infinite combo %d", 15));
numberInfinitePieces++; numberInfinitePieces++;
} }
@ -951,11 +1016,26 @@ public abstract class AbstractCommander extends Constructed {
|| cn.equals("winota, joiner of forces") || cn.equals("winota, joiner of forces")
|| cn.equals("yuriko, the tiger's shadow")) { || cn.equals("yuriko, the tiger's shadow")) {
thisMaxPower = Math.max(thisMaxPower, 20); thisMaxPower = Math.max(thisMaxPower, 20);
cardStates.add(String.format("game changer %d", 20));
} }
// keep card's level
if (!cardStates.isEmpty()) {
foundInfo.add(String.format("+%d from <b>%s</b> (%s)",
thisMaxPower,
card.getName(),
String.join(", ", cardStates)
));
foundPowerCards.add(card.getName());
} }
edhPowerLevel += thisMaxPower;
} // cards list
ObjectColor color = null; ObjectColor color = null;
for (Card commander : deck.getSideboard()) { for (Card commander : deck.getSideboard()) {
List<String> commanderStates = new ArrayList<>();
int thisMaxPower = 0; int thisMaxPower = 0;
String cn = commander.getName().toLowerCase(Locale.ENGLISH); String cn = commander.getName().toLowerCase(Locale.ENGLISH);
if (color == null) { if (color == null) {
@ -1024,6 +1104,7 @@ public abstract class AbstractCommander extends Constructed {
|| cn.equals("vorinclex, voice of hunger") || cn.equals("vorinclex, voice of hunger")
|| cn.equals("zur the enchanter")) { || cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 25); thisMaxPower = Math.max(thisMaxPower, 25);
commanderStates.add(String.format("not fun commander (+%d)", 25));
} }
// Saltiest commanders // Saltiest commanders
@ -1060,12 +1141,30 @@ public abstract class AbstractCommander extends Constructed {
|| cn.equals("xanathar, guild kingpin") || cn.equals("xanathar, guild kingpin")
|| cn.equals("zur the enchanter")) { || cn.equals("zur the enchanter")) {
thisMaxPower = Math.max(thisMaxPower, 20); thisMaxPower = Math.max(thisMaxPower, 20);
commanderStates.add(String.format("saltiest commander (+%d)", 20));
} }
// keep commander's level
if (!commanderStates.isEmpty()) {
foundInfo.add(String.format("+%d from <b>%s</b> (%s)",
thisMaxPower,
commander.getName(),
String.join(", ", commanderStates)
));
foundPowerCards.add(commander.getName());
}
edhPowerLevel += thisMaxPower; edhPowerLevel += thisMaxPower;
} }
if (numberInfinitePieces > 0) {
edhPowerLevel += numberInfinitePieces * 18; edhPowerLevel += numberInfinitePieces * 18;
edhPowerLevel = Math.round(edhPowerLevel / 10); foundInfo.add(String.format("+%d from <b>%d infinite pieces</b>", numberInfinitePieces * 18, numberInfinitePieces));
}
// block colored decks by table's edh power level were disabled
// it's better to show real edh power level and allow for direct values control
if (false) {
if (edhPowerLevel >= 100) { if (edhPowerLevel >= 100) {
edhPowerLevel = 99; edhPowerLevel = 99;
} }
@ -1076,6 +1175,7 @@ public abstract class AbstractCommander extends Constructed {
edhPowerLevel += (color.isRed() ? 10000 : 0); edhPowerLevel += (color.isRed() ? 10000 : 0);
edhPowerLevel += (color.isGreen() ? 1000 : 0); edhPowerLevel += (color.isGreen() ? 1000 : 0);
} }
}
return edhPowerLevel; return edhPowerLevel;
} }
} }

View file

@ -122,7 +122,7 @@ public class Brawl extends Constructed {
&& !(colorIdentity.isColorless() && !(colorIdentity.isColorless()
&& basicsInDeck.size() == 1 && basicsInDeck.size() == 1
&& basicsInDeck.contains(card.getName()))) { && basicsInDeck.contains(card.getName()))) {
addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (need " + colorIdentity + ", but get " + card.getColorIdentity() + ")", true);
valid = false; valid = false;
} }
} }
@ -131,7 +131,7 @@ public class Brawl extends Constructed {
&& !(colorIdentity.isColorless() && !(colorIdentity.isColorless()
&& basicsInDeck.size() == 1 && basicsInDeck.size() == 1
&& basicsInDeck.contains(card.getName()))) { && basicsInDeck.contains(card.getName()))) {
addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + colorIdentity.toString() + ')', true); addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (need " + colorIdentity + ", but get " + card.getColorIdentity() + ")", true);
valid = false; valid = false;
} }
} }

View file

@ -189,7 +189,7 @@ public class Oathbreaker extends Constructed {
for (Card card : deck.getCards()) { for (Card card : deck.getCards()) {
if (!ManaUtil.isColorIdentityCompatible(allCommandersColor, card.getColorIdentity())) { if (!ManaUtil.isColorIdentityCompatible(allCommandersColor, card.getColorIdentity())) {
addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (" + card.getColorIdentity() + ')', true); addError(DeckValidatorErrorType.OTHER, card.getName(), "Invalid color (need " + allCommandersColor + ", but get " + card.getColorIdentity() + ")", true);
valid = false; valid = false;
} }
} }

View file

@ -153,11 +153,11 @@ public class Session {
if (userName.length() > config.getMaxUserNameLength()) { if (userName.length() > config.getMaxUserNameLength()) {
return "User name may not be longer than " + config.getMaxUserNameLength() + " characters"; return "User name may not be longer than " + config.getMaxUserNameLength() + " characters";
} }
if (userName.length() <= 3) { if (userName.length() <= 2) {
return "User name is too short (3 characters or fewer)"; return "User name is too short (2 characters or fewer)";
} }
if (userName.length() >= 500) { if (userName.length() >= 250) {
return "User name is too long (500 characters or more)"; return "User name is too long (250 characters or more)";
} }
return null; return null;
} }

View file

@ -325,7 +325,7 @@ public class TableController {
// user - restrict by deck power level and cards colors (see edh power level for details) // user - restrict by deck power level and cards colors (see edh power level for details)
int edhPowerLevel = table.getMatch().getOptions().getEdhPowerLevel(); int edhPowerLevel = table.getMatch().getOptions().getEdhPowerLevel();
if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase(Locale.ENGLISH).equals("commander")) { if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase(Locale.ENGLISH).equals("commander")) {
int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck); int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck, new ArrayList<>(), new ArrayList<>());
if (deckEdhPowerLevel % 100 > edhPowerLevel) { if (deckEdhPowerLevel % 100 > edhPowerLevel) {
String message = new StringBuilder("Your deck appears to be too powerful for this table.\n\nReduce the number of extra turn cards, infect, counters, fogs, reconsider your commander. ") String message = new StringBuilder("Your deck appears to be too powerful for this table.\n\nReduce the number of extra turn cards, infect, counters, fogs, reconsider your commander. ")
.append("\nThe table requirement has a maximum power level of ").append(edhPowerLevel).append(" whilst your deck has a calculated power level of ") .append("\nThe table requirement has a maximum power level of ").append(edhPowerLevel).append(" whilst your deck has a calculated power level of ")

View file

@ -1,7 +1,5 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.common.CantBeCounteredSourceAbility; import mage.abilities.common.CantBeCounteredSourceAbility;
import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -10,10 +8,11 @@ import mage.constants.CardType;
import mage.constants.ComparisonType; import mage.constants.ComparisonType;
import mage.filter.common.FilterNonlandPermanent; import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.target.common.TargetNonlandPermanent; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class AbruptDecay extends CardImpl { public final class AbruptDecay extends CardImpl {
@ -32,7 +31,7 @@ public final class AbruptDecay extends CardImpl {
// Destroy target nonland permanent with converted mana cost 3 or less. // Destroy target nonland permanent with converted mana cost 3 or less.
this.getSpellAbility().addEffect(new DestroyTargetEffect()); this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addTarget(new TargetNonlandPermanent(filter)); this.getSpellAbility().addTarget(new TargetPermanent(filter));
} }
private AbruptDecay(final AbruptDecay card) { private AbruptDecay(final AbruptDecay card) {

View file

@ -6,8 +6,9 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
@ -53,7 +54,7 @@ public final class AbzanMonument extends CardImpl {
); );
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
ability.addCost(new SacrificeSourceCost()); ability.addCost(new SacrificeSourceCost());
this.addAbility(ability.addHint(GreatestToughnessAmongControlledCreaturesValue.ALL.getHint())); this.addAbility(ability.addHint(GreatestAmongPermanentsValue.TOUGHNESS_CONTROLLED_CREATURES.getHint()));
} }
private AbzanMonument(final AbzanMonument card) { private AbzanMonument(final AbzanMonument card) {
@ -85,8 +86,7 @@ class AbzanMonumentEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
return new SpiritXXToken( int value = GreatestAmongPermanentsValue.TOUGHNESS_CONTROLLED_CREATURES.calculate(game, source, this);
GreatestToughnessAmongControlledCreaturesValue.ALL.calculate(game, source, this) return new CreateTokenEffect(new SpiritXXToken(value)).apply(game, source);
).putOntoBattlefield(1, game, source);
} }
} }

View file

@ -1,8 +1,6 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.HighestManaValueCount;
import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
@ -10,19 +8,23 @@ import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/** /**
*
* @author nigelzor * @author nigelzor
*/ */
public final class AcceleratedMutation extends CardImpl { public final class AcceleratedMutation extends CardImpl {
private static final DynamicValue xValue = new HighestManaValueCount();
public AcceleratedMutation(UUID ownerId, CardSetInfo setInfo) { public AcceleratedMutation(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{G}{G}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{G}{G}");
// Target creature gets +X/+X until end of turn, where X is the highest converted mana cost among permanents you control. // Target creature gets +X/+X until end of turn, where X is the greatest converted mana cost among permanents you control.
this.getSpellAbility().addEffect(new BoostTargetEffect(xValue, xValue, Duration.EndOfTurn)); this.getSpellAbility().addEffect(new BoostTargetEffect(
GreatestAmongPermanentsValue.MANAVALUE_CONTROLLED_PERMANENTS,
GreatestAmongPermanentsValue.MANAVALUE_CONTROLLED_PERMANENTS,
Duration.EndOfTurn
));
this.getSpellAbility().addHint(GreatestAmongPermanentsValue.MANAVALUE_CONTROLLED_PERMANENTS.getHint());
this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addTarget(new TargetCreaturePermanent());
} }

View file

@ -2,9 +2,10 @@ package mage.cards.a;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition; import mage.abilities.condition.common.AttachedAttackingCondition;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.decorator.ConditionalContinuousEffect;
import mage.abilities.decorator.ConditionalRequirementEffect;
import mage.abilities.effects.common.combat.MustBeBlockedByAtLeastOneAttachedEffect; import mage.abilities.effects.common.combat.MustBeBlockedByAtLeastOneAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
@ -14,8 +15,6 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID; import java.util.UUID;
@ -27,12 +26,14 @@ public final class AcesBaseballBat extends CardImpl {
private static final FilterControlledCreaturePermanent filterLegendary private static final FilterControlledCreaturePermanent filterLegendary
= new FilterControlledCreaturePermanent("legendary creature"); = new FilterControlledCreaturePermanent("legendary creature");
static { static {
filterLegendary.add(SuperType.LEGENDARY.getPredicate()); filterLegendary.add(SuperType.LEGENDARY.getPredicate());
} }
private static final FilterControlledCreaturePermanent filterDalek private static final FilterControlledCreaturePermanent filterDalek
= new FilterControlledCreaturePermanent("a Dalek"); = new FilterControlledCreaturePermanent("a Dalek");
static { static {
filterDalek.add(SubType.DALEK.getPredicate()); filterDalek.add(SubType.DALEK.getPredicate());
} }
@ -48,12 +49,19 @@ public final class AcesBaseballBat extends CardImpl {
// As long as equipped creature is attacking, it has first strike and must be blocked by a Dalek if able. // As long as equipped creature is attacking, it has first strike and must be blocked by a Dalek if able.
Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect(
new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT), new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT),
AttachedToAttackingCondition.instance, "As long as equipped creature is attacking, it has first strike")); AttachedAttackingCondition.instance, "as long as equipped creature is attacking, it has first strike"
ability.addEffect(new MustBeBlockedByAtLeastOneAttachedEffect(filterDalek).concatBy("and")); ));
ability.addEffect(new ConditionalRequirementEffect(
new MustBeBlockedByAtLeastOneAttachedEffect(filterDalek),
AttachedAttackingCondition.instance, "and must be blocked by a Dalek if able"
));
this.addAbility(ability); this.addAbility(ability);
// Equip legendary creature (1) // Equip legendary creature (1)
this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(1), new TargetControlledCreaturePermanent(filterLegendary), false)); this.addAbility(new EquipAbility(
Outcome.AddAbility, new GenericManaCost(1),
new TargetControlledCreaturePermanent(filterLegendary), false
));
// Equip {3} // Equip {3}
this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(3), false)); this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(3), false));
@ -68,19 +76,3 @@ public final class AcesBaseballBat extends CardImpl {
return new AcesBaseballBat(this); return new AcesBaseballBat(this);
} }
} }
enum AttachedToAttackingCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Permanent attachment = game.getPermanent(source.getSourceId());
if (attachment == null || attachment.getAttachedTo() == null) {
return false;
}
Permanent attachedTo = game.getPermanent(attachment.getAttachedTo());
if (attachedTo == null) {
return false;
}
return attachedTo.isAttacking();
}
}

View file

@ -21,6 +21,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -82,7 +83,7 @@ class AchHansRunEffect extends OneShotEffect {
if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
return false; return false;
} }
Permanent creature = game.getPermanent(card.getId()); Permanent creature = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (creature == null) { if (creature == null) {
return false; return false;
} }

View file

@ -1,8 +1,5 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
@ -12,19 +9,15 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.FilterPermanent; import mage.filter.StaticFilters;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author Loki * @author Loki
*/ */
public final class AcidWebSpider extends CardImpl { public final class AcidWebSpider extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("Equipment");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public AcidWebSpider(UUID ownerId, CardSetInfo setInfo) { public AcidWebSpider(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}");
@ -34,7 +27,7 @@ public final class AcidWebSpider extends CardImpl {
this.toughness = new MageInt(5); this.toughness = new MageInt(5);
this.addAbility(ReachAbility.getInstance()); this.addAbility(ReachAbility.getInstance());
Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), true); Ability ability = new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect(), true);
ability.addTarget(new TargetPermanent(filter)); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_EQUIPMENT));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -1,13 +1,12 @@
package mage.cards.a; package mage.cards.a;
import java.util.*;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.effects.common.continuous.BoostAllEffect;
import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
@ -18,11 +17,12 @@ import mage.game.Game;
import mage.game.events.DamagedPlayerEvent; import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.common.TargetNonlandPermanent; import mage.target.TargetPermanent;
import mage.watchers.Watcher; import mage.watchers.Watcher;
import java.util.*;
/** /**
*
* @author TheElk801 * @author TheElk801
*/ */
public final class AdmiralBeckettBrass extends CardImpl { public final class AdmiralBeckettBrass extends CardImpl {
@ -33,7 +33,7 @@ public final class AdmiralBeckettBrass extends CardImpl {
static { static {
filter.add(SubType.PIRATE.getPredicate()); filter.add(SubType.PIRATE.getPredicate());
filter.add(TargetController.YOU.getControllerPredicate()); filter.add(TargetController.YOU.getControllerPredicate());
filter2.add(new ControllerDealtDamageByPiratesPredicate()); filter2.add(ControllerDealtDamageByPiratesPredicate.instance);
} }
public AdmiralBeckettBrass(UUID ownerId, CardSetInfo setInfo) { public AdmiralBeckettBrass(UUID ownerId, CardSetInfo setInfo) {
@ -50,7 +50,7 @@ public final class AdmiralBeckettBrass extends CardImpl {
// At the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn. // At the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
Ability ability = new BeginningOfEndStepTriggeredAbility(new GainControlTargetEffect(Duration.Custom, true)); Ability ability = new BeginningOfEndStepTriggeredAbility(new GainControlTargetEffect(Duration.Custom, true));
ability.addTarget(new TargetNonlandPermanent(filter2)); ability.addTarget(new TargetPermanent(filter2));
this.addAbility(ability, new DamagedByPiratesWatcher()); this.addAbility(ability, new DamagedByPiratesWatcher());
} }
@ -101,7 +101,8 @@ class DamagedByPiratesWatcher extends Watcher {
} }
} }
class ControllerDealtDamageByPiratesPredicate implements Predicate<Permanent> { enum ControllerDealtDamageByPiratesPredicate implements Predicate<Permanent> {
instance;
@Override @Override
public boolean apply(Permanent input, Game game) { public boolean apply(Permanent input, Game game) {

View file

@ -0,0 +1,45 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.common.DrawDiscardControllerEffect;
import mage.abilities.keyword.CrewAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AdventurersAirship extends CardImpl {
public AdventurersAirship(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
this.subtype.add(SubType.VEHICLE);
this.power = new MageInt(3);
this.toughness = new MageInt(2);
// Flying
this.addAbility(FlyingAbility.getInstance());
// When this Vehicle attacks, draw a card then discard a card.
this.addAbility(new AttacksTriggeredAbility(new DrawDiscardControllerEffect(1, 1)));
// Crew 2
this.addAbility(new CrewAbility(2));
}
private AdventurersAirship(final AdventurersAirship card) {
super(card);
}
@Override
public AdventurersAirship copy() {
return new AdventurersAirship(this);
}
}

View file

@ -0,0 +1,45 @@
package mage.cards.a;
import mage.abilities.Mode;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.TapTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.counters.CounterType;
import mage.game.permanent.token.HeroToken;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AerithRescueMission extends CardImpl {
public AerithRescueMission(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}");
// Choose one--
// * Take the Elevator -- Create three 1/1 colorless Hero creature tokens.
this.getSpellAbility().addEffect(new CreateTokenEffect(new HeroToken(), 3));
this.getSpellAbility().withFirstModeFlavorWord("Take the Elevator");
// * Take 59 Flights of Stairs -- Tap up to three target creatures. Put a stun counter on one of them.
this.getSpellAbility().addMode(new Mode(new TapTargetEffect())
.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance())
.setText("Put a stun counter on one of them"))
.addTarget(new TargetCreaturePermanent(0, 3))
.withFlavorWord("Take 59 Flights of Stairs"));
}
private AerithRescueMission(final AerithRescueMission card) {
super(card);
}
@Override
public AerithRescueMission copy() {
return new AerithRescueMission(this);
}
}

View file

@ -14,12 +14,11 @@ import mage.constants.SubType;
import mage.filter.common.FilterNonlandPermanent; import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.permanent.token.BirdToken; import mage.game.permanent.token.BirdToken;
import mage.target.common.TargetNonlandPermanent; import mage.target.TargetPermanent;
import java.util.UUID; import java.util.UUID;
/** /**
*
* @author weirddan455 * @author weirddan455
*/ */
public final class AetherChanneler extends CardImpl { public final class AetherChanneler extends CardImpl {
@ -44,7 +43,7 @@ public final class AetherChanneler extends CardImpl {
// * Draw a card. // * Draw a card.
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BirdToken())); Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BirdToken()));
Mode mode = new Mode(new ReturnToHandTargetEffect()); Mode mode = new Mode(new ReturnToHandTargetEffect());
mode.addTarget(new TargetNonlandPermanent(filter)); mode.addTarget(new TargetPermanent(filter));
ability.addMode(mode); ability.addMode(mode);
ability.addMode(new Mode(new DrawCardSourceControllerEffect(1))); ability.addMode(new Mode(new DrawCardSourceControllerEffect(1)));
this.addAbility(ability); this.addAbility(ability);

View file

@ -1,15 +1,15 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.target.common.TargetNonlandPermanent; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class AetherGale extends CardImpl { public final class AetherGale extends CardImpl {
@ -17,10 +17,9 @@ public final class AetherGale extends CardImpl {
public AetherGale(UUID ownerId, CardSetInfo setInfo) { public AetherGale(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{U}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{U}");
// Return six target nonland permanents to their owners' hands. // Return six target nonland permanents to their owners' hands.
this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); this.getSpellAbility().addEffect(new ReturnToHandTargetEffect());
this.getSpellAbility().addTarget(new TargetNonlandPermanent(6,6, StaticFilters.FILTER_PERMANENTS_NON_LAND, false)); this.getSpellAbility().addTarget(new TargetPermanent(6, StaticFilters.FILTER_PERMANENTS_NON_LAND));
} }
private AetherGale(final AetherGale card) { private AetherGale(final AetherGale card) {

View file

@ -17,6 +17,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/** /**
* *
@ -73,7 +74,7 @@ class AethermagesTouchEffect extends OneShotEffect {
cards.remove(card); cards.remove(card);
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
// It gains \"At the beginning of your end step, return this creature to its owner's hand.\" // It gains \"At the beginning of your end step, return this creature to its owner's hand.\"
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent != null) { if (permanent != null) {
Ability ability = new BeginningOfEndStepTriggeredAbility(TargetController.YOU, new ReturnToHandSourceEffect(true), false, null); Ability ability = new BeginningOfEndStepTriggeredAbility(TargetController.YOU, new ReturnToHandSourceEffect(true), false, null);
ContinuousEffect effect = new GainAbilityTargetEffect(ability, Duration.Custom); ContinuousEffect effect = new GainAbilityTargetEffect(ability, Duration.Custom);

View file

@ -0,0 +1,78 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Controllable;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.Optional;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AettirAndPriwen extends CardImpl {
public AettirAndPriwen(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.EQUIPMENT);
// Equipped creature has base power and toughness X/X, where X is your life total.
this.addAbility(new SimpleStaticAbility(new AettirAndPriwenEffect()));
// Equip {5}
this.addAbility(new EquipAbility(5));
}
private AettirAndPriwen(final AettirAndPriwen card) {
super(card);
}
@Override
public AettirAndPriwen copy() {
return new AettirAndPriwen(this);
}
}
class AettirAndPriwenEffect extends ContinuousEffectImpl {
AettirAndPriwenEffect() {
super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.Benefit);
staticText = "equipped creature has base power and toughness X/X, where X is your life total";
}
private AettirAndPriwenEffect(final AettirAndPriwenEffect effect) {
super(effect);
}
@Override
public AettirAndPriwenEffect copy() {
return new AettirAndPriwenEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null) {
return false;
}
int life = Optional
.ofNullable(source)
.map(Controllable::getControllerId)
.map(game::getPlayer)
.map(Player::getLife)
.orElse(0);
permanent.getPower().setModifiedBaseValue(life);
permanent.getToughness().setModifiedBaseValue(life);
return true;
}
}

View file

@ -1,31 +1,26 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.constants.SubType;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.condition.common.DidNotAttackThisTurnEnchantedCondition; import mage.abilities.condition.common.DidNotAttackThisTurnEnchantedCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.DestroyAttachedToEffect; import mage.abilities.effects.common.DestroyAttachedToEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.constants.Outcome;
import mage.target.TargetPermanent;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.FirstStrikeAbility; import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.TrampleAbility;
import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.AttachmentType; import mage.constants.*;
import mage.constants.CardType;
import mage.constants.TargetController;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.watchers.common.AttackedThisTurnWatcher; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author jeffwadsworth * @author jeffwadsworth
*/ */
public final class Aggression extends CardImpl { public final class Aggression extends CardImpl {
@ -45,27 +40,25 @@ public final class Aggression extends CardImpl {
TargetPermanent auraTarget = new TargetPermanent(filter); TargetPermanent auraTarget = new TargetPermanent(filter);
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget); this.addAbility(new EnchantAbility(auraTarget));
this.addAbility(ability);
// Enchanted creature has first strike and trample. // Enchanted creature has first strike and trample.
Ability ability2 = new SimpleStaticAbility( Ability ability = new SimpleStaticAbility(
new GainAbilityAttachedEffect( new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.AURA)
FirstStrikeAbility.getInstance(), );
AttachmentType.AURA)); ability.addEffect(new GainAbilityAttachedEffect(
ability2.addEffect(new GainAbilityAttachedEffect( TrampleAbility.getInstance(), AttachmentType.AURA
TrampleAbility.getInstance(), ).setText("and trample"));
AttachmentType.AURA).setText("and trample")); this.addAbility(ability);
this.addAbility(ability2);
// At the beginning of the end step of enchanted creature's controller, destroy that creature if it didn't attack this turn. // At the beginning of the end step of enchanted creature's controller, destroy that creature if it didn't attack this turn.
this.addAbility(new ConditionalTriggeredAbility( this.addAbility(new BeginningOfEndStepTriggeredAbility(
new AtTheBeginOfNextEndStepDelayedTriggeredAbility( TargetController.CONTROLLER_ATTACHED_TO,
new DestroyAttachedToEffect("enchanted"), new ConditionalOneShotEffect(
TargetController.CONTROLLER_ATTACHED_TO), new DestroyAttachedToEffect(""), DidNotAttackThisTurnEnchantedCondition.instance,
DidNotAttackThisTurnEnchantedCondition.instance, "destroy that creature if it didn't attack this turn"
"At the beginning of the end step of enchanted creature's controller, destroy that creature if it didn't attack this turn.")); ), false
));
} }
private Aggression(final Aggression card) { private Aggression(final Aggression card) {

View file

@ -0,0 +1,52 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class Ahriman extends CardImpl {
public Ahriman(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
this.subtype.add(SubType.EYE);
this.subtype.add(SubType.HORROR);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Deathtouch
this.addAbility(DeathtouchAbility.getInstance());
// {3}, Sacrifice another creature or artifact: Draw a card.
Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new GenericManaCost(3));
ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE_OR_ARTIFACT));
this.addAbility(ability);
}
private Ahriman(final Ahriman card) {
super(card);
}
@Override
public Ahriman copy() {
return new Ahriman(this);
}
}

View file

@ -76,7 +76,7 @@ class AidFromTheCowlEffect extends OneShotEffect {
if (card.isPermanent(game) && controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " onto the battlefield?", source, game)) { if (card.isPermanent(game) && controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " onto the battlefield?", source, game)) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game); controller.moveCards(card, Zone.BATTLEFIELD, source, game);
} else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) { } else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getIdName() + " on the bottom of your library?", source, game)) {
controller.putCardsOnBottomOfLibrary(card, game, source, false); controller.putCardsOnBottomOfLibrary(card, game, source);
} else { } else {
game.informPlayers(controller.getLogName() + " puts the revealed card back to the top of the library."); game.informPlayers(controller.getLogName() + " puts the revealed card back to the top of the library.");
} }

View file

@ -0,0 +1,55 @@
package mage.cards.a;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.keyword.CyclingAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterPermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AirshipCrash extends CardImpl {
private static final FilterPermanent filter
= new FilterPermanent("artifact, enchantment, or creature with flying");
static {
filter.add(Predicates.or(
CardType.ARTIFACT.getPredicate(),
CardType.ENCHANTMENT.getPredicate(),
Predicates.and(
CardType.CREATURE.getPredicate(),
new AbilityPredicate(FlyingAbility.class)
)
));
}
public AirshipCrash(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}");
// Destroy target artifact, enchantment, or creature with flying.
this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addTarget(new TargetPermanent(filter));
// Cycling {2}
this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}")));
}
private AirshipCrash(final AirshipCrash card) {
super(card);
}
@Override
public AirshipCrash copy() {
return new AirshipCrash(this);
}
}

View file

@ -90,7 +90,7 @@ class AjaniSleeperAgentEffect extends OneShotEffect {
if (card.isCreature(game) || card.isPlaneswalker(game)) { if (card.isCreature(game) || card.isPlaneswalker(game)) {
controller.moveCards(card, Zone.HAND, source, game); controller.moveCards(card, Zone.HAND, source, game);
} else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getName() + " on the bottom of your library?", source, game)) { } else if (controller.chooseUse(Outcome.Neutral, "Put " + card.getName() + " on the bottom of your library?", source, game)) {
controller.putCardsOnBottomOfLibrary(card, game, source, true); controller.putCardsOnBottomOfLibrary(card, game, source);
} }
return true; return true;
} }

View file

@ -13,9 +13,8 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.common.FilterEquipmentPermanent; import mage.filter.StaticFilters;
import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.permanent.AttachedToPredicate;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.events.DefenderAttackedEvent; import mage.game.events.DefenderAttackedEvent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
@ -103,21 +102,11 @@ class AkiriFearlessVoyagerTriggeredAbility extends TriggeredAbilityImpl {
class AkiriFearlessVoyagerEffect extends OneShotEffect { class AkiriFearlessVoyagerEffect extends OneShotEffect {
private static enum AkiriFearlessVoyagerPredicate implements ObjectSourcePlayerPredicate<Permanent> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
return game.getPermanent(input.getObject().getAttachedTo()) != null
&& game.getControllerId(input.getObject().getAttachedTo()).equals(input.getPlayerId());
}
}
private static final FilterPermanent filter private static final FilterPermanent filter
= new FilterEquipmentPermanent("equipment attached to a creature you control"); = new FilterPermanent(SubType.EQUIPMENT, "equipment attached to a creature you control");
static { static {
filter.add(AkiriFearlessVoyagerPredicate.instance); filter.add(new AttachedToPredicate(StaticFilters.FILTER_CONTROLLED_CREATURE));
} }
AkiriFearlessVoyagerEffect() { AkiriFearlessVoyagerEffect() {

View file

@ -1,34 +1,38 @@
package mage.cards.a; package mage.cards.a;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.MageObjectReference;
import mage.Mana; import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.effects.Effect; import mage.abilities.hint.Hint;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.PartnerAbility; import mage.abilities.keyword.PartnerAbility;
import mage.abilities.mana.DynamicManaAbility; import mage.abilities.mana.DynamicManaAbility;
import mage.constants.*;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.game.Game; import mage.constants.CardType;
import mage.game.events.GameEvent; import mage.constants.SubType;
import mage.game.events.ZoneChangeEvent; import mage.constants.SuperType;
import mage.watchers.Watcher; import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.EnteredThisTurnPredicate;
import java.util.UUID;
/** /**
* @author TheElk801 * @author TheElk801
*/ */
public final class AlenaKessigTrapper extends CardImpl { public final class AlenaKessigTrapper extends CardImpl {
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creatures you control that entered this turn");
static {
filter.add(EnteredThisTurnPredicate.instance);
}
private static final GreatestAmongPermanentsValue xValue = new GreatestAmongPermanentsValue(GreatestAmongPermanentsValue.Quality.Power, filter);
private static final Hint hint = xValue.getHint();
public AlenaKessigTrapper(UUID ownerId, CardSetInfo setInfo) { public AlenaKessigTrapper(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}");
@ -43,9 +47,9 @@ public final class AlenaKessigTrapper extends CardImpl {
// {T}: Add an amount of {R} equal to the greatest power among creatures you control that entered the battlefield this turn. // {T}: Add an amount of {R} equal to the greatest power among creatures you control that entered the battlefield this turn.
this.addAbility(new DynamicManaAbility( this.addAbility(new DynamicManaAbility(
Mana.RedMana(1), AlenaKessigTrapperValue.instance, new TapSourceCost(), "Add an amount of {R} " + Mana.RedMana(1), xValue, new TapSourceCost(),
"equal to the greatest power among creatures you control that entered the battlefield this turn." "Add an amount of {R} equal to the greatest power among creatures you control that entered this turn."
), new AlenaKessigTrapperWatcher()); ).addHint(hint));
// Partner // Partner
this.addAbility(PartnerAbility.getInstance()); this.addAbility(PartnerAbility.getInstance());
@ -60,63 +64,3 @@ public final class AlenaKessigTrapper extends CardImpl {
return new AlenaKessigTrapper(this); return new AlenaKessigTrapper(this);
} }
} }
enum AlenaKessigTrapperValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
AlenaKessigTrapperWatcher watcher = game.getState().getWatcher(AlenaKessigTrapperWatcher.class);
if (watcher == null) {
return 0;
}
return watcher.getPower(sourceAbility.getControllerId(), game);
}
@Override
public AlenaKessigTrapperValue copy() {
return instance;
}
@Override
public String getMessage() {
return "";
}
}
class AlenaKessigTrapperWatcher extends Watcher {
private final Set<MageObjectReference> enteredThisTurn = new HashSet<>();
public AlenaKessigTrapperWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.ZONE_CHANGE
&& ((ZoneChangeEvent) event).getToZone() == Zone.BATTLEFIELD) {
enteredThisTurn.add(new MageObjectReference(event.getTargetId(), game));
}
}
@Override
public void reset() {
enteredThisTurn.clear();
super.reset();
}
int getPower(UUID playerId, Game game) {
return enteredThisTurn
.stream()
.filter(Objects::nonNull)
.map(mor -> mor.getPermanent(game))
.filter(Objects::nonNull)
.filter(permanent1 -> permanent1.isCreature(game))
.filter(permanent -> permanent.isControlledBy(playerId))
.map(MageObject::getPower)
.mapToInt(MageInt::getValue)
.max()
.orElse(0);
}
}

View file

@ -4,22 +4,24 @@ import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.hint.Hint; import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint; import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledArtifactPermanent; import mage.filter.common.FilterControlledArtifactPermanent;
import mage.filter.predicate.permanent.TappedPredicate; import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetAnyTarget; import mage.target.common.TargetAnyTarget;
import java.util.UUID; import java.util.UUID;
@ -29,6 +31,15 @@ import java.util.UUID;
*/ */
public final class AlibouAncientWitness extends CardImpl { public final class AlibouAncientWitness extends CardImpl {
private static final FilterPermanent filter = new FilterControlledArtifactPermanent("tapped artifacts you control");
static {
filter.add(TappedPredicate.TAPPED);
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null);
private static final Hint hint = new ValueHint("Tapped artifacts you control", xValue);
public AlibouAncientWitness(UUID ownerId, CardSetInfo setInfo) { public AlibouAncientWitness(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}{W}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}{W}");
@ -45,11 +56,12 @@ public final class AlibouAncientWitness extends CardImpl {
// Whenever one or more artifact creatures you control attack, Alibou, Ancient Witness deals X damage to any target and you scry X, where X is the number of tapped artifacts you control. // Whenever one or more artifact creatures you control attack, Alibou, Ancient Witness deals X damage to any target and you scry X, where X is the number of tapped artifacts you control.
Ability ability = new AttacksWithCreaturesTriggeredAbility( Ability ability = new AttacksWithCreaturesTriggeredAbility(
new AlibouAncientWitnessEffect(), 1, new DamageTargetEffect(xValue).setText("{this} deals X damage to any target"),
StaticFilters.FILTER_PERMANENTS_ARTIFACT_CREATURE 1, StaticFilters.FILTER_PERMANENTS_ARTIFACT_CREATURE
).setTriggerPhrase("Whenever one or more artifact creatures you control attack, "); ).setTriggerPhrase("Whenever one or more artifact creatures you control attack, ");
ability.addEffect(new ScryEffect(xValue).concatBy("and you"));
ability.addTarget(new TargetAnyTarget()); ability.addTarget(new TargetAnyTarget());
this.addAbility(ability.addHint(AlibouAncientWitnessEffect.getHint())); this.addAbility(ability.addHint(hint));
} }
private AlibouAncientWitness(final AlibouAncientWitness card) { private AlibouAncientWitness(final AlibouAncientWitness card) {
@ -61,56 +73,3 @@ public final class AlibouAncientWitness extends CardImpl {
return new AlibouAncientWitness(this); return new AlibouAncientWitness(this);
} }
} }
class AlibouAncientWitnessEffect extends OneShotEffect {
private static final FilterPermanent filter = new FilterControlledArtifactPermanent();
static {
filter.add(TappedPredicate.TAPPED);
}
private static final Hint hint = new ValueHint(
"Tapped artifacts you control", new PermanentsOnBattlefieldCount(filter)
);
AlibouAncientWitnessEffect() {
super(Outcome.Benefit);
staticText = "{this} deals X damage to any target and you scry X, " +
"where X is the number of tapped artifacts you control";
}
private AlibouAncientWitnessEffect(final AlibouAncientWitnessEffect effect) {
super(effect);
}
@Override
public AlibouAncientWitnessEffect copy() {
return new AlibouAncientWitnessEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
int xValue = game.getBattlefield().count(filter, source.getControllerId(), source, game);
if (xValue < 1) {
return false;
}
Permanent permanent = game.getPermanent(source.getFirstTarget());
if (permanent != null) {
permanent.damage(xValue, source.getSourceId(), source, game);
}
Player player = game.getPlayer(source.getFirstTarget());
if (player != null) {
player.damage(xValue, source.getSourceId(), source, game);
}
player = game.getPlayer(source.getControllerId());
if (player != null) {
player.scry(xValue, source, game);
}
return true;
}
public static Hint getHint() {
return hint;
}
}

View file

@ -71,7 +71,7 @@ class AmassTheComponentsEffect extends OneShotEffect {
if (player.choose(Outcome.Detriment, player.getHand(), target, source, game)) { if (player.choose(Outcome.Detriment, player.getHand(), target, source, game)) {
Card card = player.getHand().get(target.getFirstTarget(), game); Card card = player.getHand().get(target.getFirstTarget(), game);
if (card != null) { if (card != null) {
return player.putCardsOnBottomOfLibrary(card, game, source, true); return player.putCardsOnBottomOfLibrary(card, game, source);
} }
} }
} }

View file

@ -39,10 +39,12 @@ public final class AmbitiousDragonborn extends CardImpl {
this.toughness = new MageInt(0); this.toughness = new MageInt(0);
// Ambitious Dragonborn enters the battlefield with X +1/+1 counters on it, where X is the greatest power among creatures you control and creature cards in your graveyard. // Ambitious Dragonborn enters the battlefield with X +1/+1 counters on it, where X is the greatest power among creatures you control and creature cards in your graveyard.
this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect( this.addAbility(new EntersBattlefieldAbility(
new AddCountersSourceEffect(
CounterType.P1P1.createInstance(), AmbitiousDragonbornValue.instance, false CounterType.P1P1.createInstance(), AmbitiousDragonbornValue.instance, false
), "with X +1/+1 counters on it, where X is the greatest power " + ), "with X +1/+1 counters on it, where X is the greatest power " +
"among creatures you control and creature cards in your graveyard")); "among creatures you control and creature cards in your graveyard"
).addHint(hint));
} }
private AmbitiousDragonborn(final AmbitiousDragonborn card) { private AmbitiousDragonborn(final AmbitiousDragonborn card) {

View file

@ -0,0 +1,148 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.keyword.WardAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.TreasureToken;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AncientAdamantoise extends CardImpl {
public AncientAdamantoise(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{G}{G}");
this.subtype.add(SubType.TURTLE);
this.power = new MageInt(8);
this.toughness = new MageInt(20);
// Vigilance
this.addAbility(VigilanceAbility.getInstance());
// Ward {3}
this.addAbility(new WardAbility(new ManaCostsImpl<>("{3}")));
// Damage isn't removed from this creature during cleanup steps.
this.addAbility(new SimpleStaticAbility(new AncientAdamantoiseDamageEffect()));
// All damage that would be dealt to you and other permanents you control is dealt to this creature instead.
this.addAbility(new SimpleStaticAbility(new AncientAdamantoiseRedirectEffect()));
// When this creature dies, exile it and create ten tapped Treasure tokens.
Ability ability = new DiesSourceTriggeredAbility(new ExileSourceEffect().setText("exile it"));
ability.addEffect(new CreateTokenEffect(new TreasureToken(), 10, true).concatBy("and"));
this.addAbility(ability);
}
private AncientAdamantoise(final AncientAdamantoise card) {
super(card);
}
@Override
public AncientAdamantoise copy() {
return new AncientAdamantoise(this);
}
}
class AncientAdamantoiseDamageEffect extends ContinuousRuleModifyingEffectImpl {
AncientAdamantoiseDamageEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "damage isn't removed from {this} during cleanup steps";
}
private AncientAdamantoiseDamageEffect(final AncientAdamantoiseDamageEffect effect) {
super(effect);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.REMOVE_DAMAGE_EOT;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return event.getTargetId().equals(source.getSourceId());
}
@Override
public AncientAdamantoiseDamageEffect copy() {
return new AncientAdamantoiseDamageEffect(this);
}
}
class AncientAdamantoiseRedirectEffect extends ReplacementEffectImpl {
AncientAdamantoiseRedirectEffect() {
super(Duration.WhileOnBattlefield, Outcome.RedirectDamage);
staticText = "all damage that would be dealt to you and other " +
"permanents you control is dealt to this creature instead";
}
private AncientAdamantoiseRedirectEffect(final AncientAdamantoiseRedirectEffect effect) {
super(effect);
}
@Override
public AncientAdamantoiseRedirectEffect copy() {
return new AncientAdamantoiseRedirectEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
DamageEvent damageEvent = (DamageEvent) event;
Permanent permanent = source.getSourcePermanentOrLKI(game);
if (permanent != null) {
permanent.damage(
damageEvent.getAmount(), event.getSourceId(), source, game,
damageEvent.isCombatDamage(), damageEvent.isPreventable()
);
}
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
switch (event.getType()) {
case DAMAGE_PLAYER:
case DAMAGE_PERMANENT:
return true;
default:
return false;
}
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
switch (event.getType()) {
case DAMAGE_PLAYER:
return source.isControlledBy(event.getTargetId());
case DAMAGE_PERMANENT:
return !event.getTargetId().equals(source.getSourceId())
&& source.isControlledBy(game.getControllerId(event.getTargetId()));
default:
return false;
}
}
}

View file

@ -77,7 +77,7 @@ class AnimistsAwakeningEffect extends OneShotEffect {
if (SpellMasteryCondition.instance.apply(game, source)) { if (SpellMasteryCondition.instance.apply(game, source)) {
for (Card card : toBattlefield) { for (Card card : toBattlefield) {
Permanent land = game.getPermanent(card.getId()); Permanent land = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (land != null) { if (land != null) {
land.untap(game); land.untap(game);
} }

View file

@ -157,7 +157,7 @@ class AnzragsRampageEffect extends OneShotEffect {
} }
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent == null) { if (permanent == null) {
return true; return true;
} }

View file

@ -25,6 +25,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/** /**
* *
@ -80,7 +81,7 @@ class ApprenticeNecromancerEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null && card != null) { if (controller != null && card != null) {
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) { if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
Permanent creature = game.getPermanent(card.getId()); Permanent creature = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (creature != null) { if (creature != null) {
// Gains haste // Gains haste
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);

View file

@ -19,6 +19,7 @@ import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/** /**
* *
@ -86,7 +87,7 @@ class ArbiterOfTheIdealEffect extends OneShotEffect {
controller.revealCards(sourceObject.getIdName(), new CardsImpl(card), game); controller.revealCards(sourceObject.getIdName(), new CardsImpl(card), game);
if (filter.match(card, game) && controller.chooseUse(outcome, "Put " + card.getName() + " onto the battlefield?", source, game)) { if (filter.match(card, game) && controller.chooseUse(outcome, "Put " + card.getName() + " onto the battlefield?", source, game)) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game); controller.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent != null) { if (permanent != null) {
permanent.addCounters(CounterType.MANIFESTATION.createInstance(), source.getControllerId(), source, game); permanent.addCounters(CounterType.MANIFESTATION.createInstance(), source.getControllerId(), source, game);
ContinuousEffect effect = new AddCardTypeTargetEffect(Duration.Custom, CardType.ENCHANTMENT); ContinuousEffect effect = new AddCardTypeTargetEffect(Duration.Custom, CardType.ENCHANTMENT);

View file

@ -3,7 +3,7 @@ package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.Mana; import mage.Mana;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.mana.AnyColorManaAbility; import mage.abilities.mana.AnyColorManaAbility;
import mage.abilities.mana.DynamicManaAbility; import mage.abilities.mana.DynamicManaAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -31,10 +31,10 @@ public final class ArborAdherent extends CardImpl {
// {T}: Add X mana of any one color, where X is the greatest toughness among other creatures you control. // {T}: Add X mana of any one color, where X is the greatest toughness among other creatures you control.
this.addAbility(new DynamicManaAbility( this.addAbility(new DynamicManaAbility(
Mana.AnyMana(1), GreatestToughnessAmongControlledCreaturesValue.OTHER, Mana.AnyMana(1), GreatestAmongPermanentsValue.TOUGHNESS_OTHER_CONTROLLED_CREATURES,
new TapSourceCost(), "add X mana of any one color, where X is the " + new TapSourceCost(), "add X mana of any one color, where X is the " +
"greatest toughness among other creatures you control", true "greatest toughness among other creatures you control", true
).addHint(GreatestToughnessAmongControlledCreaturesValue.OTHER.getHint())); ).addHint(GreatestAmongPermanentsValue.TOUGHNESS_OTHER_CONTROLLED_CREATURES.getHint()));
} }
private ArborAdherent(final ArborAdherent card) { private ArborAdherent(final ArborAdherent card) {

View file

@ -1,8 +1,5 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
@ -12,17 +9,17 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.filter.FilterPermanent;
import mage.filter.common.FilterLandPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetLandPermanent;
import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public final class ArborElf extends CardImpl { public final class ArborElf extends CardImpl {
private static final FilterLandPermanent filter = new FilterLandPermanent(SubType.FOREST, "Forest"); private static final FilterPermanent filter = new FilterPermanent(SubType.FOREST, "Forest");
public ArborElf(UUID ownerId, CardSetInfo setInfo) { public ArborElf(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}");
@ -34,8 +31,7 @@ public final class ArborElf extends CardImpl {
// (T): Untap target Forest. // (T): Untap target Forest.
Ability ability = new SimpleActivatedAbility(new UntapTargetEffect(), new TapSourceCost()); Ability ability = new SimpleActivatedAbility(new UntapTargetEffect(), new TapSourceCost());
TargetLandPermanent target = new TargetLandPermanent(filter); ability.addTarget(new TargetPermanent(filter));
ability.addTarget(target);
this.addAbility(ability); this.addAbility(ability);
} }
@ -47,5 +43,4 @@ public final class ArborElf extends CardImpl {
public ArborElf copy() { public ArborElf copy() {
return new ArborElf(this); return new ArborElf(this);
} }
} }

View file

@ -1,7 +1,6 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.AttacksTriggeredAbility;
@ -13,10 +12,11 @@ import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.TargetController; import mage.constants.TargetController;
import mage.filter.common.FilterNonlandPermanent; import mage.filter.common.FilterNonlandPermanent;
import mage.target.common.TargetNonlandPermanent; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class ArchonOfTheTriumvirate extends CardImpl { public final class ArchonOfTheTriumvirate extends CardImpl {
@ -40,7 +40,7 @@ public final class ArchonOfTheTriumvirate extends CardImpl {
// Whenever Archon of the Triumvirate attacks, detain up to two target nonland permanents your opponents control. // Whenever Archon of the Triumvirate attacks, detain up to two target nonland permanents your opponents control.
// (Until your next turn, those permanents can't attack or block and their activated abilities can't be activated.) // (Until your next turn, those permanents can't attack or block and their activated abilities can't be activated.)
Ability ability = new AttacksTriggeredAbility(new DetainTargetEffect(), false); Ability ability = new AttacksTriggeredAbility(new DetainTargetEffect(), false);
ability.addTarget(new TargetNonlandPermanent(0,2,filter, false)); ability.addTarget(new TargetPermanent(0, 2, filter));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -88,7 +88,7 @@ class ArdentDustspeakerCost extends CostImpl {
if (controller != null && (controller.chooseTarget(Outcome.Benefit, this.getTargets().get(0), source, game))) { if (controller != null && (controller.chooseTarget(Outcome.Benefit, this.getTargets().get(0), source, game))) {
Card card = game.getCard(this.getTargets().get(0).getFirstTarget()); Card card = game.getCard(this.getTargets().get(0).getFirstTarget());
if (card != null) { if (card != null) {
controller.putCardsOnBottomOfLibrary(card, game, source, true); controller.putCardsOnBottomOfLibrary(card, game, source);
paid = true; paid = true;
} }
} }

View file

@ -19,7 +19,7 @@ import mage.filter.common.FilterNonlandPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.target.common.TargetNonlandPermanent; import mage.target.TargetPermanent;
import java.util.UUID; import java.util.UUID;
@ -53,7 +53,7 @@ public final class ArgentDais extends CardImpl {
); );
ability.addCost(new TapSourceCost()); ability.addCost(new TapSourceCost());
ability.addCost(new RemoveCountersSourceCost(CounterType.OIL.createInstance(2))); ability.addCost(new RemoveCountersSourceCost(CounterType.OIL.createInstance(2)));
ability.addTarget(new TargetNonlandPermanent(filter)); ability.addTarget(new TargetPermanent(filter));
ability.addEffect(new DrawCardTargetControllerEffect(2)); ability.addEffect(new DrawCardTargetControllerEffect(2));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -1,35 +1,27 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DamageTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledArtifactPermanent;
import mage.target.common.TargetAttackingCreature; import mage.target.common.TargetAttackingCreature;
import java.util.UUID;
/** /**
*
* @author Plopman * @author Plopman
*/ */
public final class ArmedResponse extends CardImpl { public final class ArmedResponse extends CardImpl {
private static final FilterControlledArtifactPermanent filter = new FilterControlledArtifactPermanent("Equipment you control");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public ArmedResponse(UUID ownerId, CardSetInfo setInfo) { public ArmedResponse(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{W}");
// Armed Response deals damage to target attacking creature equal to the number of Equipment you control. // Armed Response deals damage to target attacking creature equal to the number of Equipment you control.
Effect effect = new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter)); Effect effect = new DamageTargetEffect(new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_EQUIPMENT));
effect.setText("{this} deals damage to target attacking creature equal to the number of Equipment you control"); effect.setText("{this} deals damage to target attacking creature equal to the number of Equipment you control");
this.getSpellAbility().addEffect(effect); this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addTarget(new TargetAttackingCreature()); this.getSpellAbility().addTarget(new TargetAttackingCreature());

View file

@ -18,6 +18,7 @@ import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -94,7 +95,7 @@ class ArmoredSkyhunterEffect extends OneShotEffect {
} }
player.moveCards(card, Zone.BATTLEFIELD, source, game); player.moveCards(card, Zone.BATTLEFIELD, source, game);
cards.removeIf(uuid -> game.getState().getZone(uuid) != Zone.LIBRARY); cards.removeIf(uuid -> game.getState().getZone(uuid) != Zone.LIBRARY);
Permanent equipment = game.getPermanent(card.getId()); Permanent equipment = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (equipment == null || !equipment.hasSubtype(SubType.EQUIPMENT, game)) { if (equipment == null || !equipment.hasSubtype(SubType.EQUIPMENT, game)) {
return player.putCardsOnBottomOfLibrary(cards, game, source, false); return player.putCardsOnBottomOfLibrary(cards, game, source, false);
} }

View file

@ -9,7 +9,7 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.filter.FilterPermanent; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
@ -21,12 +21,6 @@ import java.util.UUID;
*/ */
public final class ArmoryAutomaton extends CardImpl { public final class ArmoryAutomaton extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("Equipment");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public ArmoryAutomaton(UUID ownerId, CardSetInfo setInfo) { public ArmoryAutomaton(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}");
@ -36,7 +30,7 @@ public final class ArmoryAutomaton extends CardImpl {
// Whenever Armory Automaton enters or attacks, you may attach any number of target Equipment to it. // Whenever Armory Automaton enters or attacks, you may attach any number of target Equipment to it.
Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ArmoryAutomatonEffect(), true); Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new ArmoryAutomatonEffect(), true);
ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, filter)); ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, StaticFilters.FILTER_PERMANENT_EQUIPMENT));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -4,6 +4,7 @@ import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect;
import mage.abilities.keyword.BoastAbility; import mage.abilities.keyword.BoastAbility;
@ -11,11 +12,9 @@ import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
/** /**
@ -36,7 +35,8 @@ public final class ArniBrokenbrow extends CardImpl {
this.addAbility(HasteAbility.getInstance()); this.addAbility(HasteAbility.getInstance());
// Boast {1}: You may change Arni Brokenbrow's base power to 1 plus the greatest power among other creatures you control until end of turn. // Boast {1}: You may change Arni Brokenbrow's base power to 1 plus the greatest power among other creatures you control until end of turn.
this.addAbility(new BoastAbility(new ArniBrokenbrowEffect(), new GenericManaCost(1))); this.addAbility(new BoastAbility(new ArniBrokenbrowEffect(), new GenericManaCost(1))
.addHint(GreatestAmongPermanentsValue.POWER_OTHER_CONTROLLED_CREATURES.getHint()));
} }
private ArniBrokenbrow(final ArniBrokenbrow card) { private ArniBrokenbrow(final ArniBrokenbrow card) {
@ -72,17 +72,7 @@ class ArniBrokenbrowEffect extends OneShotEffect {
if (controller == null || mageObject == null) { if (controller == null || mageObject == null) {
return false; return false;
} }
int power = game int power = GreatestAmongPermanentsValue.POWER_OTHER_CONTROLLED_CREATURES.calculate(game, source, this);
.getBattlefield()
.getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, source.getControllerId(), game)
.stream()
.filter(Objects::nonNull)
.filter(permanent -> !permanent.getId().equals(source.getSourceId())
|| permanent.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter())
.map(MageObject::getPower)
.mapToInt(MageInt::getValue)
.max()
.orElse(0);
power += 1; power += 1;
if (controller.chooseUse(outcome, "Change base power of " + mageObject.getLogName() + " to " if (controller.chooseUse(outcome, "Change base power of " + mageObject.getLogName() + " to "
+ power + " until end of turn?", source, game + power + " until end of turn?", source, game

View file

@ -16,6 +16,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard; import mage.target.TargetCard;
import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInHand;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -131,7 +132,7 @@ class ArniMetalbrowEffect extends OneShotEffect {
return false; return false;
} }
player.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); player.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null);
Permanent creature = game.getPermanent(card.getId()); Permanent creature = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (creature != null) { if (creature != null) {
game.getCombat().addAttackingCreature(creature.getId(), game); game.getCombat().addAttackingCreature(creature.getId(), game);
} }

View file

@ -2,7 +2,7 @@ package mage.cards.a;
import mage.Mana; import mage.Mana;
import mage.abilities.common.SagaAbility; import mage.abilities.common.SagaAbility;
import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.effects.common.FightTargetsEffect; import mage.abilities.effects.common.FightTargetsEffect;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect;
@ -58,11 +58,11 @@ public final class ArniSlaysTheTroll extends CardImpl {
// III You gain life equal to the greatest power among creatures you control. // III You gain life equal to the greatest power among creatures you control.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III,
new GainLifeEffect(GreatestPowerAmongControlledCreaturesValue.instance, new GainLifeEffect(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES,
"You gain life equal to the greatest power among creatures you control" "You gain life equal to the greatest power among creatures you control"
) )
); );
sagaAbility.addHint(GreatestPowerAmongControlledCreaturesValue.getHint()); sagaAbility.addHint(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES.getHint());
this.addAbility(sagaAbility); this.addAbility(sagaAbility);
} }

View file

@ -19,6 +19,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/** /**
* *
@ -111,7 +112,7 @@ class ArthurMarigoldKnightEffect extends OneShotEffect {
)) { )) {
return player.putCardsOnBottomOfLibrary(cards, game, source, false); return player.putCardsOnBottomOfLibrary(cards, game, source, false);
} }
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent == null) { if (permanent == null) {
return player.putCardsOnBottomOfLibrary(cards, game, source, false); return player.putCardsOnBottomOfLibrary(cards, game, source, false);
} }

View file

@ -1,39 +1,34 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.filter.FilterPermanent; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class ArtificersHex extends CardImpl { public final class ArtificersHex extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("Equipment");
static {
filter.add(CardType.ARTIFACT.getPredicate());
filter.add(SubType.EQUIPMENT.getPredicate());
}
public ArtificersHex(UUID ownerId, CardSetInfo setInfo) { public ArtificersHex(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}");
this.subtype.add(SubType.AURA); this.subtype.add(SubType.AURA);
// Enchant Equipment // Enchant Equipment
TargetPermanent auraTarget = new TargetPermanent(filter); TargetPermanent auraTarget = new TargetPermanent(StaticFilters.FILTER_PERMANENT_EQUIPMENT);
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature));
Ability ability = new EnchantAbility(auraTarget); Ability ability = new EnchantAbility(auraTarget);

View file

@ -22,6 +22,7 @@ import mage.target.TargetPermanent;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -172,7 +173,7 @@ class AscentOfTheWorthyReturnEffect extends OneShotEffect {
countersToAdd.addCounter(CounterType.FLYING.createInstance()); countersToAdd.addCounter(CounterType.FLYING.createInstance());
game.setEnterWithCounters(card.getId(), countersToAdd); game.setEnterWithCounters(card.getId(), countersToAdd);
player.moveCards(card, Zone.BATTLEFIELD, source, game); player.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent == null) { if (permanent == null) {
return false; return false;
} }

View file

@ -0,0 +1,127 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.SacrificePermanentTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.GainAbilityControlledSpellsEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.CasualtyAbility;
import mage.constants.*;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.counters.CounterType;
import mage.filter.common.FilterNonlandCard;
import mage.filter.StaticFilters;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author padfoothelix
*/
public final class AshadTheLoneCyberman extends CardImpl {
private static final String rule = "The first nonlegendary artifact spell you cast each turn has casualty 2. " +
"<i>(As you cast it, you may sacrifice a creature with power 2 or greater. When you do, copy it. " +
"A copy of an artifact spell becomes a token.)</i>";
private static final FilterNonlandCard filter = new FilterNonlandCard("the first nonlegendary artifact you cast each turn");
static {
filter.add(AshadTheLoneCybermanSpellPredicate.instance);
}
public AshadTheLoneCyberman(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}{B}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.CYBERMAN);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// The first nonlegendary artifact spell you cast each turn has casualty 2.
this.addAbility(new SimpleStaticAbility(
new GainAbilityControlledSpellsEffect(
new CasualtyAbility(2),
filter
).setText(rule)),
new AshadTheLoneCybermanWatcher()
);
// Whenever you sacrifice another creature, put a +1/+1 counter on Ashad, the Lone Cyberman.
this.addAbility(new SacrificePermanentTriggeredAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance()),
StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE
));
}
private AshadTheLoneCyberman(final AshadTheLoneCyberman card) {
super(card);
}
@Override
public AshadTheLoneCyberman copy() {
return new AshadTheLoneCyberman(this);
}
}
class AshadTheLoneCybermanWatcher extends Watcher {
// Based on Peri Brown which is based on Conduit of Ruin
private final Map<UUID, Integer> nonlegendaryArtifactSpells; // player id -> number
public AshadTheLoneCybermanWatcher() {
super(WatcherScope.GAME);
nonlegendaryArtifactSpells = new HashMap<>();
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
Spell spell = (Spell) game.getObject(event.getTargetId());
if (spell != null
&& !spell.isLegendary(game)
&& spell.isArtifact(game)) {
nonlegendaryArtifactSpells.put(event.getPlayerId(), nonlegendaryArtifactSpellsCastThisTurn(event.getPlayerId()) + 1);
}
}
}
public int nonlegendaryArtifactSpellsCastThisTurn(UUID playerId) {
return nonlegendaryArtifactSpells.getOrDefault(playerId, 0);
}
@Override
public void reset() {
super.reset();
nonlegendaryArtifactSpells.clear();
}
}
enum AshadTheLoneCybermanSpellPredicate implements ObjectSourcePlayerPredicate<Card> {
instance;
@Override
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {
if (input.getObject() != null
&& !input.getObject().isLegendary(game)
&& input.getObject().isArtifact(game)) {
AshadTheLoneCybermanWatcher watcher = game.getState().getWatcher(AshadTheLoneCybermanWatcher.class);
return watcher != null && watcher.nonlegendaryArtifactSpellsCastThisTurn(input.getPlayerId()) == 0;
}
return false;
}
@Override
public String toString() {
return "The first nonlegendary artifact spell you cast each turn";
}
}

View file

@ -0,0 +1,46 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.PutCards;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class AshePrincessOfDalmasca extends CardImpl {
public AshePrincessOfDalmasca(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.REBEL);
this.subtype.add(SubType.NOBLE);
this.power = new MageInt(3);
this.toughness = new MageInt(2);
// Whenever Ashe attacks, look at the top five cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
this.addAbility(new AttacksTriggeredAbility(new LookLibraryAndPickControllerEffect(
5, 1, StaticFilters.FILTER_CARD_ARTIFACT_AN,
PutCards.HAND, PutCards.BOTTOM_RANDOM
)));
}
private AshePrincessOfDalmasca(final AshePrincessOfDalmasca card) {
super(card);
}
@Override
public AshePrincessOfDalmasca copy() {
return new AshePrincessOfDalmasca(this);
}
}

View file

@ -125,7 +125,7 @@ class AshiokNightmareWeaverPutIntoPlayEffect extends OneShotEffect {
return true; return true;
} }
controller.moveCards(card, Zone.BATTLEFIELD, source, game); controller.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent != null) { if (permanent != null) {
game.addEffect(new AddCardSubTypeTargetEffect( game.addEffect(new AddCardSubTypeTargetEffect(
SubType.NIGHTMARE, Duration.EndOfTurn SubType.NIGHTMARE, Duration.EndOfTurn

View file

@ -13,6 +13,7 @@ import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -81,7 +82,8 @@ class AspiringChampionEffect extends OneShotEffect {
toReveal.retainZone(Zone.LIBRARY, game); toReveal.retainZone(Zone.LIBRARY, game);
player.putCardsOnBottomOfLibrary(toReveal, game, source, false); player.putCardsOnBottomOfLibrary(toReveal, game, source, false);
player.shuffleLibrary(source, game); player.shuffleLibrary(source, game);
Permanent creature = game.getPermanent(card.getId()); game.processAction();
Permanent creature = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (creature == null if (creature == null
|| !creature.hasSubtype(SubType.DEMON, game) || !creature.hasSubtype(SubType.DEMON, game)
|| creature.getPower().getValue() < 1) { || creature.getPower().getValue() < 1) {

View file

@ -1,42 +1,30 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID; import mage.abilities.effects.common.AttachTargetToTargetEffect;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.TargetController; import mage.filter.FilterPermanent;
import mage.filter.common.FilterEnchantmentPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/** /**
*
* @author North * @author North
*/ */
public final class AuraFinesse extends CardImpl { public final class AuraFinesse extends CardImpl {
private static final FilterEnchantmentPermanent filter = new FilterEnchantmentPermanent("Aura you control"); private static final FilterPermanent filter = new FilterControlledPermanent(SubType.AURA);
static {
filter.add(TargetController.YOU.getControllerPredicate());
filter.add(SubType.AURA.getPredicate());
}
public AuraFinesse(UUID ownerId, CardSetInfo setInfo) { public AuraFinesse(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}");
// Attach target Aura you control to target creature. // Attach target Aura you control to target creature.
this.getSpellAbility().addEffect(new AuraFinesseEffect()); this.getSpellAbility().addEffect(new AttachTargetToTargetEffect());
this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addTarget(new TargetPermanent(filter));
this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addTarget(new TargetCreaturePermanent());
@ -53,47 +41,3 @@ public final class AuraFinesse extends CardImpl {
return new AuraFinesse(this); return new AuraFinesse(this);
} }
} }
class AuraFinesseEffect extends OneShotEffect {
AuraFinesseEffect() {
super(Outcome.BoostCreature);
this.staticText = "Attach target Aura you control to target creature";
}
private AuraFinesseEffect(final AuraFinesseEffect effect) {
super(effect);
}
@Override
public AuraFinesseEffect copy() {
return new AuraFinesseEffect(this);
}
// 15/06/2010 As Aura Finesse resolves, if either target is illegal,
// the spell resolves but the Aura doesnt move. You still draw a card.
// If both targets are illegal, Aura Finesse doesnt resolve and you dont draw a card.
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Permanent aura = game.getPermanent(source.getFirstTarget());
Permanent creature = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (aura != null && creature != null) {
Permanent oldCreature = game.getPermanent(aura.getAttachedTo());
if (oldCreature != null && !oldCreature.equals(creature)) {
Target auraTarget = aura.getSpellAbility().getTargets().get(0);
if (!auraTarget.canTarget(creature.getId(), game)) {
game.informPlayers(aura.getLogName() + " was not attched to " +creature.getLogName() + " because it's no legal target for the aura" );
} else if (oldCreature.removeAttachment(aura.getId(), source, game)) {
game.informPlayers(aura.getLogName() + " was unattached from " + oldCreature.getLogName() + " and attached to " + creature.getLogName());
creature.addAttachment(aura.getId(), source, game);
}
}
}
return true;
}
}

View file

@ -1,37 +1,26 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachTargetToTargetEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledPermanent; import mage.target.TargetPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetControlledPermanent;
import java.util.UUID;
/** /**
*
* @author Plopman * @author Plopman
*/ */
public final class AuriokWindwalker extends CardImpl { public final class AuriokWindwalker extends CardImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent("Equipment you control");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public AuriokWindwalker(UUID ownerId, CardSetInfo setInfo) { public AuriokWindwalker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.HUMAN);
@ -41,9 +30,10 @@ public final class AuriokWindwalker extends CardImpl {
// Flying // Flying
this.addAbility(FlyingAbility.getInstance()); this.addAbility(FlyingAbility.getInstance());
// {T}: Attach target Equipment you control to target creature you control. // {T}: Attach target Equipment you control to target creature you control.
Ability ability = new SimpleActivatedAbility(new AttachTargetEquipmentEffect(), new TapSourceCost()); Ability ability = new SimpleActivatedAbility(new AttachTargetToTargetEffect(), new TapSourceCost());
ability.addTarget(new TargetControlledPermanent(filter)); ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_EQUIPMENT));
ability.addTarget(new TargetControlledCreaturePermanent()); ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(ability); this.addAbility(ability);
} }
@ -57,30 +47,3 @@ public final class AuriokWindwalker extends CardImpl {
return new AuriokWindwalker(this); return new AuriokWindwalker(this);
} }
} }
class AttachTargetEquipmentEffect extends OneShotEffect {
AttachTargetEquipmentEffect() {
super(Outcome.BoostCreature);
staticText = "Attach target Equipment you control to target creature you control";
}
private AttachTargetEquipmentEffect(final AttachTargetEquipmentEffect effect) {
super(effect);
}
@Override
public AttachTargetEquipmentEffect copy() {
return new AttachTargetEquipmentEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent equipment = game.getPermanent(source.getFirstTarget());
Permanent creature = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if (creature != null && equipment != null) {
return creature.addAttachment(equipment.getId(), source, game);
}
return false;
}
}

View file

@ -1,9 +1,6 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.continuous.BecomesCreatureAttachedEffect; import mage.abilities.effects.common.continuous.BecomesCreatureAttachedEffect;
@ -11,37 +8,38 @@ import mage.abilities.keyword.EnchantAbility;
import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.filter.common.FilterLandPermanent; import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.TokenImpl;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetLandPermanent;
import java.util.UUID;
/** /**
*
* @author jeffwadsworth * @author jeffwadsworth
*/ */
public final class AwakenTheAncient extends CardImpl { public final class AwakenTheAncient extends CardImpl {
private static final FilterLandPermanent filter = new FilterLandPermanent(SubType.MOUNTAIN, "Mountain"); private static final FilterPermanent filter = new FilterPermanent(SubType.MOUNTAIN, "Mountain");
public AwakenTheAncient(UUID ownerId, CardSetInfo setInfo) { public AwakenTheAncient(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{R}{R}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}{R}{R}");
this.subtype.add(SubType.AURA); this.subtype.add(SubType.AURA);
// Enchant Mountain // Enchant Mountain
TargetPermanent auraTarget = new TargetLandPermanent(filter); TargetPermanent auraTarget = new TargetPermanent(filter);
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.PutCreatureInPlay)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.PutCreatureInPlay));
Ability ability = new EnchantAbility(auraTarget); this.addAbility(new EnchantAbility(auraTarget));
this.addAbility(ability);
// Enchanted Mountain is a 7/7 red Giant creature with haste. It's still a land. // Enchanted Mountain is a 7/7 red Giant creature with haste. It's still a land.
Ability ability2 = new SimpleStaticAbility(new BecomesCreatureAttachedEffect( this.addAbility(new SimpleStaticAbility(new BecomesCreatureAttachedEffect(
new GiantToken(), "Enchanted Mountain is a 7/7 red Giant creature with haste. It's still a land", Duration.WhileOnBattlefield, BecomesCreatureAttachedEffect.LoseType.COLOR)); new GiantToken(), "Enchanted Mountain is a 7/7 red Giant creature with haste. It's still a land",
this.addAbility(ability2); Duration.WhileOnBattlefield, BecomesCreatureAttachedEffect.LoseType.COLOR
)));
} }
private AwakenTheAncient(final AwakenTheAncient card) { private AwakenTheAncient(final AwakenTheAncient card) {

View file

@ -1,29 +1,26 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect; import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.common.FilterLandPermanent; import mage.filter.FilterPermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.TokenImpl;
import mage.target.common.TargetLandPermanent; import mage.target.TargetPermanent;
import java.util.UUID;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public final class AwakenerDruid extends CardImpl { public final class AwakenerDruid extends CardImpl {
private static final FilterLandPermanent filter = new FilterLandPermanent(SubType.FOREST, "Forest"); private static final FilterPermanent filter = new FilterPermanent(SubType.FOREST, "Forest");
public AwakenerDruid(UUID ownerId, CardSetInfo setInfo) { public AwakenerDruid(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
@ -35,7 +32,7 @@ public final class AwakenerDruid extends CardImpl {
// When Awakener Druid enters the battlefield, target Forest becomes a 4/5 green Treefolk creature for as long as Awakener Druid remains on the battlefield. It's still a land. // When Awakener Druid enters the battlefield, target Forest becomes a 4/5 green Treefolk creature for as long as Awakener Druid remains on the battlefield. It's still a land.
Ability ability = new EntersBattlefieldTriggeredAbility(new AwakenerDruidBecomesCreatureEffect(), false); Ability ability = new EntersBattlefieldTriggeredAbility(new AwakenerDruidBecomesCreatureEffect(), false);
ability.addTarget(new TargetLandPermanent(filter)); ability.addTarget(new TargetPermanent(filter));
this.addAbility(ability); this.addAbility(ability);
} }
@ -87,6 +84,7 @@ class AwakenerDruidToken extends TokenImpl {
power = new MageInt(4); power = new MageInt(4);
toughness = new MageInt(5); toughness = new MageInt(5);
} }
private AwakenerDruidToken(final AwakenerDruidToken token) { private AwakenerDruidToken(final AwakenerDruidToken token) {
super(token); super(token);
} }

View file

@ -15,9 +15,11 @@ import mage.constants.*;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -90,17 +92,18 @@ class AyaraFurnaceQueenEffect extends OneShotEffect {
if (player == null || card == null) { if (player == null || card == null) {
return false; return false;
} }
player.moveCards(card, Zone.BATTLEFIELD, source, game); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (game.getPermanent(card.getId()) == null) { if (permanent == null) {
return false; return false;
} }
game.addEffect(new GainAbilityTargetEffect( game.addEffect(new GainAbilityTargetEffect(
HasteAbility.getInstance(), Duration.Custom HasteAbility.getInstance(), Duration.Custom
).setTargetPointer(new FixedTarget(card.getId(), game)), source); ).setTargetPointer(new FixedTarget(permanent.getId(), game)), source);
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
new ExileTargetEffect().setText("exile it") new ExileTargetEffect().setText("exile it")
.setTargetPointer(new FixedTarget(card.getId(), game)), .setTargetPointer(new FixedTarget(permanent.getId(), game)),
TargetController.ANY TargetController.ANY
), source); ), source);
return true;} return true;
}
} }

View file

@ -19,6 +19,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -79,7 +80,7 @@ class BackForMoreEffect extends OneShotEffect {
return false; return false;
} }
player.moveCards(card, Zone.BATTLEFIELD, source, game); player.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent == null) { if (permanent == null) {
return false; return false;
} }

View file

@ -0,0 +1,71 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.SagaAbility;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.ExileSourceAndReturnFaceUpEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.common.counter.AddCountersAllEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BahamutWardenOfLight extends CardImpl {
public BahamutWardenOfLight(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.SAGA);
this.subtype.add(SubType.DRAGON);
this.power = new MageInt(5);
this.toughness = new MageInt(5);
this.nightCard = true;
this.color.setWhite(true);
// (As this Saga enters and after your draw step, add a lore counter.)
SagaAbility sagaAbility = new SagaAbility(this);
// I, II -- Wings of Light -- Put a +1/+1 counter on each other creature you control. Those creatures gain flying until end of turn.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, ability -> {
ability.addEffect(new AddCountersAllEffect(
CounterType.P1P1.createInstance(), StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE
));
ability.addEffect(new GainAbilityControlledEffect(
FlyingAbility.getInstance(), Duration.EndOfTurn,
StaticFilters.FILTER_CONTROLLED_CREATURE
).setText("Those creatures gain flying until end of turn"));
ability.withFlavorWord("Wings of Light");
});
// III -- Gigaflare -- Destroy target permanent. Exile Bahamut, then return it to the battlefield.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, ability -> {
ability.addEffect(new DestroyTargetEffect());
ability.addEffect(new ExileSourceAndReturnFaceUpEffect());
ability.addTarget(new TargetPermanent());
ability.withFlavorWord("Gigaflare");
});
this.addAbility(sagaAbility);
// Flying
this.addAbility(FlyingAbility.getInstance());
}
private BahamutWardenOfLight(final BahamutWardenOfLight card) {
super(card);
}
@Override
public BahamutWardenOfLight copy() {
return new BahamutWardenOfLight(this);
}
}

View file

@ -0,0 +1,48 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.CrewAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BalambGardenAirborne extends CardImpl {
public BalambGardenAirborne(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.VEHICLE);
this.power = new MageInt(5);
this.toughness = new MageInt(4);
this.nightCard = true;
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever Balamb Garden attacks, draw a card.
this.addAbility(new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(1)));
// Crew 1
this.addAbility(new CrewAbility(1));
}
private BalambGardenAirborne(final BalambGardenAirborne card) {
super(card);
}
@Override
public BalambGardenAirborne copy() {
return new BalambGardenAirborne(this);
}
}

View file

@ -0,0 +1,88 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTappedAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.TransformSourceEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.TransformAbility;
import mage.abilities.mana.BlueManaAbility;
import mage.abilities.mana.GreenManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BalambGardenSeeDAcademy extends CardImpl {
public BalambGardenSeeDAcademy(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
this.subtype.add(SubType.TOWN);
this.secondSideCardClazz = mage.cards.b.BalambGardenAirborne.class;
// This land enters tapped.
this.addAbility(new EntersBattlefieldTappedAbility());
// Add {G} or {U}.
this.addAbility(new GreenManaAbility());
this.addAbility(new BlueManaAbility());
// {5}{G}{U}, {T}: Transform this land. This ability costs {1} less to activate for each other Town you control.
this.addAbility(new TransformAbility());
Ability ability = new SimpleActivatedAbility(new TransformSourceEffect(), new ManaCostsImpl<>("{5}{G}{U}"));
ability.addCost(new TapSourceCost());
ability.addEffect(new InfoEffect("This ability costs {1} less to activate for each other Town you control"));
this.addAbility(ability
.setCostAdjuster(BalambGardenSeeDAcademyAdjuster.instance)
.addHint(BalambGardenSeeDAcademyAdjuster.getHint()));
}
private BalambGardenSeeDAcademy(final BalambGardenSeeDAcademy card) {
super(card);
}
@Override
public BalambGardenSeeDAcademy copy() {
return new BalambGardenSeeDAcademy(this);
}
}
enum BalambGardenSeeDAcademyAdjuster implements CostAdjuster {
instance;
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.TOWN);
static {
filter.add(AnotherPredicate.instance);
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
private static final Hint hint = new ValueHint("Other Towns you control", xValue);
static Hint getHint() {
return hint;
}
@Override
public void reduceCost(Ability ability, Game game) {
int count = xValue.calculate(game, ability, null);
CardUtil.reduceCost(ability, count);
}
}

View file

@ -4,7 +4,8 @@ import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.EquippedMultipleSourceCondition; import mage.abilities.condition.Condition;
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.decorator.ConditionalContinuousEffect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
@ -15,6 +16,8 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.predicate.permanent.AttachedToSourcePredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
@ -25,7 +28,14 @@ import java.util.UUID;
*/ */
public final class BalanWanderingKnight extends CardImpl { public final class BalanWanderingKnight extends CardImpl {
private static final String rule = "{this} has double strike as long as two or more Equipment are attached to it."; private static final FilterPermanent filter = new FilterPermanent(SubType.EQUIPMENT, "");
static {
filter.add(AttachedToSourcePredicate.instance);
}
private static final Condition condition
= new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.MORE_THAN, 1, false);
public BalanWanderingKnight(UUID ownerId, CardSetInfo setInfo) { public BalanWanderingKnight(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{W}");
@ -38,8 +48,10 @@ public final class BalanWanderingKnight extends CardImpl {
this.addAbility(FirstStrikeAbility.getInstance()); this.addAbility(FirstStrikeAbility.getInstance());
// Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it. // Balan, Wandering Knight has double strike as long as two or more Equipment are attached to it.
ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance()), EquippedMultipleSourceCondition.instance, rule); this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect(
this.addAbility(new SimpleStaticAbility(effect)); new GainAbilitySourceEffect(DoubleStrikeAbility.getInstance()), condition,
"{this} has double strike as long as two or more Equipment are attached to it."
)));
// {1}{W}: Attach all Equipment you control to Balan. // {1}{W}: Attach all Equipment you control to Balan.
this.addAbility(new SimpleActivatedAbility(new BalanWanderingKnightEffect(), new ManaCostsImpl<>("{1}{W}"))); this.addAbility(new SimpleActivatedAbility(new BalanWanderingKnightEffect(), new ManaCostsImpl<>("{1}{W}")));
@ -54,11 +66,13 @@ public final class BalanWanderingKnight extends CardImpl {
return new BalanWanderingKnight(this); return new BalanWanderingKnight(this);
} }
static class BalanWanderingKnightEffect extends OneShotEffect { }
public BalanWanderingKnightEffect() { class BalanWanderingKnightEffect extends OneShotEffect {
BalanWanderingKnightEffect() {
super(Outcome.Benefit); super(Outcome.Benefit);
this.staticText = "Attach all Equipment you control to {this}."; this.staticText = "attach all Equipment you control to {this}.";
} }
private BalanWanderingKnightEffect(final BalanWanderingKnightEffect effect) { private BalanWanderingKnightEffect(final BalanWanderingKnightEffect effect) {
@ -72,21 +86,15 @@ public final class BalanWanderingKnight extends CardImpl {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Permanent balan = game.getPermanent(source.getSourceId()); Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (balan != null) { if (permanent == null) {
FilterPermanent filter = new FilterPermanent(); return false;
filter.add(SubType.EQUIPMENT.getPredicate());
for (Permanent equipment : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) {
if (equipment != null) {
//If an Equipment can't equip, it isn't attached, and it doesn't become unattached (if it's attached to a creature).
if (!balan.cantBeAttachedBy(equipment, source, game, false)) {
balan.addAttachment(equipment.getId(), source, game);
}
} }
for (Permanent equipment : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_CONTROLLED_PERMANENT_EQUIPMENT, source.getControllerId(), source, game
)) {
permanent.addAttachment(equipment.getId(), source, game);
} }
return true; return true;
} }
return false;
}
}
} }

View file

@ -24,6 +24,7 @@ import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/** /**
* *
@ -98,7 +99,7 @@ class BalduvianAtrocityEffect extends OneShotEffect {
return false; return false;
} }
controller.moveCards(card, Zone.BATTLEFIELD, source, game); controller.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId()); Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent == null) { if (permanent == null) {
return false; return false;
} }

View file

@ -0,0 +1,54 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.AddCardSubtypeAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.JobSelectAbility;
import mage.abilities.keyword.ReachAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.AttachmentType;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BardsBow extends CardImpl {
public BardsBow(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{G}");
this.subtype.add(SubType.EQUIPMENT);
// Job select
this.addAbility(new JobSelectAbility());
// Equipped creature gets +2/+2, has reach, and is a Bard in addition to its other types.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2));
ability.addEffect(new GainAbilityAttachedEffect(
ReachAbility.getInstance(), AttachmentType.EQUIPMENT
).setText(", has reach"));
ability.addEffect(new AddCardSubtypeAttachedEffect(
SubType.BARD, AttachmentType.EQUIPMENT
).setText(", and is a Bard in addition to its other types"));
this.addAbility(ability);
// Perseus's Bow-- Equip {6}
this.addAbility(new EquipAbility(6).withFlavorWord("Perseus's Bow"));
}
private BardsBow(final BardsBow card) {
super(card);
}
@Override
public BardsBow copy() {
return new BardsBow(this);
}
}

View file

@ -0,0 +1,39 @@
package mage.cards.b;
import mage.abilities.common.EntersBattlefieldTappedAbility;
import mage.abilities.mana.BlueManaAbility;
import mage.abilities.mana.RedManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BaronAirshipKingdom extends CardImpl {
public BaronAirshipKingdom(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
this.subtype.add(SubType.TOWN);
// This land enters tapped.
this.addAbility(new EntersBattlefieldTappedAbility());
// {T}: Add {U} or {R}.
this.addAbility(new BlueManaAbility());
this.addAbility(new RedManaAbility());
}
private BaronAirshipKingdom(final BaronAirshipKingdom card) {
super(card);
}
@Override
public BaronAirshipKingdom copy() {
return new BaronAirshipKingdom(this);
}
}

View file

@ -3,35 +3,28 @@ package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachTargetToTargetEffect;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.keyword.ReachAbility; import mage.abilities.keyword.ReachAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.RebelRedToken; import mage.game.permanent.token.RebelRedToken;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.targetpointer.EachTargetPointer;
import java.util.List;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
/** /**
* @author TheElk801 * @author TheElk801
*/ */
public final class BarretAvalancheLeader extends CardImpl { public final class BarretAvalancheLeader extends CardImpl {
private static final FilterPermanent filter = new FilterControlledPermanent(SubType.EQUIPMENT);
private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.REBEL); private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.REBEL);
public BarretAvalancheLeader(UUID ownerId, CardSetInfo setInfo) { public BarretAvalancheLeader(UUID ownerId, CardSetInfo setInfo) {
@ -48,12 +41,12 @@ public final class BarretAvalancheLeader extends CardImpl {
// Avalanche! -- Whenever an Equipment you control enters, create a 2/2 red Rebel creature token. // Avalanche! -- Whenever an Equipment you control enters, create a 2/2 red Rebel creature token.
this.addAbility(new EntersBattlefieldAllTriggeredAbility( this.addAbility(new EntersBattlefieldAllTriggeredAbility(
new CreateTokenEffect(new RebelRedToken()), filter new CreateTokenEffect(new RebelRedToken()), StaticFilters.FILTER_CONTROLLED_PERMANENT_EQUIPMENT
).withFlavorWord("Avalanche!")); ).withFlavorWord("Avalanche!"));
// At the beginning of combat on your turn, attach up to one target Equipment you control to target Rebel you control. // At the beginning of combat on your turn, attach up to one target Equipment you control to target Rebel you control.
Ability ability = new BeginningOfCombatTriggeredAbility(new BarretAvalancheLeaderEffect()); Ability ability = new BeginningOfCombatTriggeredAbility(new AttachTargetToTargetEffect());
ability.addTarget(new TargetPermanent(0, 1, filter)); ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_CONTROLLED_PERMANENT_EQUIPMENT));
ability.addTarget(new TargetPermanent(filter2)); ability.addTarget(new TargetPermanent(filter2));
this.addAbility(ability); this.addAbility(ability);
} }
@ -67,33 +60,3 @@ public final class BarretAvalancheLeader extends CardImpl {
return new BarretAvalancheLeader(this); return new BarretAvalancheLeader(this);
} }
} }
class BarretAvalancheLeaderEffect extends OneShotEffect {
BarretAvalancheLeaderEffect() {
super(Outcome.Benefit);
staticText = "attach up to one target Equipment you control to target Rebel you control";
this.setTargetPointer(new EachTargetPointer());
}
private BarretAvalancheLeaderEffect(final BarretAvalancheLeaderEffect effect) {
super(effect);
}
@Override
public BarretAvalancheLeaderEffect copy() {
return new BarretAvalancheLeaderEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
List<Permanent> permanents = this
.getTargetPointer()
.getTargets(game, source)
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return permanents.size() >= 2 && permanents.get(1).addAttachment(permanents.get(0).getId(), source, game);
}
}

View file

@ -6,8 +6,8 @@ import mage.abilities.keyword.CyclingAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.filter.common.FilterEnchantmentPermanent; import mage.filter.StaticFilters;
import mage.target.common.TargetEnchantmentPermanent; import mage.target.TargetPermanent;
import java.util.UUID; import java.util.UUID;
@ -21,8 +21,9 @@ public final class BarrierBreach extends CardImpl {
// Exile up to three target enchantments. // Exile up to three target enchantments.
this.getSpellAbility().addEffect(new ExileTargetEffect()); this.getSpellAbility().addEffect(new ExileTargetEffect());
this.getSpellAbility().addTarget(new TargetEnchantmentPermanent(0, 3, this.getSpellAbility().addTarget(new TargetPermanent(
new FilterEnchantmentPermanent("enchantments"), false)); 0, 3, StaticFilters.FILTER_PERMANENT_ENCHANTMENTS
));
// Cycling {2} // Cycling {2}
this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}")));

View file

@ -1,28 +1,26 @@
package mage.cards.b; package mage.cards.b;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.CostAdjuster; import mage.abilities.costs.CostAdjuster;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.hint.Hint; import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.filter.FilterPermanent; import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.Wurm44Token; import mage.game.permanent.token.Wurm44Token;
import mage.util.CardUtil; import mage.util.CardUtil;
@ -34,11 +32,9 @@ import java.util.UUID;
*/ */
public final class BaruWurmspeaker extends CardImpl { public final class BaruWurmspeaker extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.WURM, "Wurms"); private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(SubType.WURM, "Wurms you control");
private static final FilterPermanent filter2 = new FilterPermanent(SubType.WURM, ""); static final GreatestAmongPermanentsValue xValue = new GreatestAmongPermanentsValue(GreatestAmongPermanentsValue.Quality.Power, filter);
private static final Hint hint = new ValueHint( private static final Hint hint = xValue.getHint();
"Highest power among Wurms you control", BaruWurmspeakerValue.instance
);
public BaruWurmspeaker(UUID ownerId, CardSetInfo setInfo) { public BaruWurmspeaker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}");
@ -54,7 +50,7 @@ public final class BaruWurmspeaker extends CardImpl {
2, 2, Duration.WhileOnBattlefield, filter, false 2, 2, Duration.WhileOnBattlefield, filter, false
)); ));
ability.addEffect(new GainAbilityControlledEffect( ability.addEffect(new GainAbilityControlledEffect(
TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter2 TrampleAbility.getInstance(), Duration.WhileOnBattlefield, filter
).setText("and have trample")); ).setText("and have trample"));
this.addAbility(ability); this.addAbility(ability);
@ -64,6 +60,7 @@ public final class BaruWurmspeaker extends CardImpl {
ability.addEffect(new InfoEffect("this ability costs {X} less to activate, " + ability.addEffect(new InfoEffect("this ability costs {X} less to activate, " +
"where X is the greatest power among Wurms you control")); "where X is the greatest power among Wurms you control"));
ability.setCostAdjuster(BaruWurmspeakerAdjuster.instance); ability.setCostAdjuster(BaruWurmspeakerAdjuster.instance);
ability.addHint(hint);
this.addAbility(ability); this.addAbility(ability);
} }
@ -77,44 +74,12 @@ public final class BaruWurmspeaker extends CardImpl {
} }
} }
enum BaruWurmspeakerValue implements DynamicValue {
instance;
private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.WURM);
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return game
.getBattlefield()
.getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game)
.stream()
.map(MageObject::getPower)
.mapToInt(MageInt::getValue)
.max()
.orElse(0);
}
@Override
public BaruWurmspeakerValue copy() {
return this;
}
@Override
public String getMessage() {
return "";
}
@Override
public String toString() {
return "";
}
}
enum BaruWurmspeakerAdjuster implements CostAdjuster { enum BaruWurmspeakerAdjuster implements CostAdjuster {
instance; instance;
@Override @Override
public void reduceCost(Ability ability, Game game) { public void reduceCost(Ability ability, Game game) {
int value = BaruWurmspeakerValue.instance.calculate(game, ability, null); int value = BaruWurmspeaker.xValue.calculate(game, ability, null);
if (value > 0) { if (value > 0) {
CardUtil.reduceCost(ability, value); CardUtil.reduceCost(ability, value);
} }

View file

@ -0,0 +1,62 @@
package mage.cards.b;
import mage.abilities.Mode;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.game.permanent.token.WaylayToken;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BattleMenu extends CardImpl {
private static final FilterPermanent filter = new FilterCreaturePermanent("creature with power 4 or greater");
static {
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3));
}
public BattleMenu(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}");
// Choose one --
// * Attack -- Create a 2/2 white Knight creature token.
this.getSpellAbility().addEffect(new CreateTokenEffect(new WaylayToken()));
this.getSpellAbility().withFirstModeFlavorWord("Attack");
// * Ability -- Target creature gets +0/+4 until end of turn.
this.getSpellAbility().addMode(new Mode(new BoostTargetEffect(0, 4))
.addTarget(new TargetCreaturePermanent())
.withFlavorWord("Ability"));
// * Magic -- Destroy target creature with power 4 or greater.
this.getSpellAbility().addMode(new Mode(new DestroyTargetEffect())
.addTarget(new TargetPermanent(filter))
.withFlavorWord("Magic"));
// * Item -- You gain 4 life.
this.getSpellAbility().addMode(new Mode(new GainLifeEffect(4)).withFlavorWord("Item"));
}
private BattleMenu(final BattleMenu card) {
super(card);
}
@Override
public BattleMenu copy() {
return new BattleMenu(this);
}
}

View file

@ -12,12 +12,13 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.choices.Choice; import mage.choices.Choice;
import mage.constants.*; import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterLandPermanent; import mage.filter.common.FilterLandPermanent;
import mage.filter.predicate.permanent.TappedPredicate; import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetLandPermanent; import mage.target.TargetPermanent;
import java.util.*; import java.util.*;
@ -26,7 +27,7 @@ import java.util.*;
*/ */
public final class BenthicExplorers extends CardImpl { public final class BenthicExplorers extends CardImpl {
private static final FilterLandPermanent filter = new FilterLandPermanent("tapped land an opponent controls"); private static final FilterPermanent filter = new FilterLandPermanent("tapped land an opponent controls");
static { static {
filter.add(TargetController.OPPONENT.getControllerPredicate()); filter.add(TargetController.OPPONENT.getControllerPredicate());
@ -43,9 +44,7 @@ public final class BenthicExplorers extends CardImpl {
// {T}, Untap a tapped land an opponent controls: Add one mana of any type that land could produce. // {T}, Untap a tapped land an opponent controls: Add one mana of any type that land could produce.
Ability ability = new BenthicExplorersManaAbility(); Ability ability = new BenthicExplorersManaAbility();
ability.addCost(new UntapTargetCost( ability.addCost(new UntapTargetCost(new TargetPermanent(filter)));
new TargetLandPermanent(filter)
));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -5,8 +5,7 @@ import mage.Mana;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue;
import mage.abilities.dynamicvalue.common.GreatestToughnessAmongControlledCreaturesValue;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.keyword.VigilanceAbility; import mage.abilities.keyword.VigilanceAbility;
import mage.abilities.mana.DynamicManaAbility; import mage.abilities.mana.DynamicManaAbility;
@ -35,16 +34,16 @@ public final class BighornerRancher extends CardImpl {
// {T}: Add an amount of {G} equal to the greatest power among creatures you control. // {T}: Add an amount of {G} equal to the greatest power among creatures you control.
this.addAbility(new DynamicManaAbility( this.addAbility(new DynamicManaAbility(
Mana.GreenMana(1), GreatestPowerAmongControlledCreaturesValue.instance, new TapSourceCost(), Mana.GreenMana(1), GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES, new TapSourceCost(),
"Add an amount of {G} equal to the greatest power among creatures you control." "Add an amount of {G} equal to the greatest power among creatures you control."
).addHint(GreatestPowerAmongControlledCreaturesValue.getHint())); ).addHint(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES.getHint()));
// Sacrifice Bighorner Rancher: You gain life equal to the greatest toughness among other creatures you control. // Sacrifice Bighorner Rancher: You gain life equal to the greatest toughness among other creatures you control.
this.addAbility(new SimpleActivatedAbility( this.addAbility(new SimpleActivatedAbility(
new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.OTHER) new GainLifeEffect(GreatestAmongPermanentsValue.TOUGHNESS_OTHER_CONTROLLED_CREATURES)
.setText("You gain life equal to the greatest toughness among other creatures you control."), .setText("You gain life equal to the greatest toughness among other creatures you control."),
new SacrificeSourceCost() new SacrificeSourceCost()
).addHint(GreatestToughnessAmongControlledCreaturesValue.OTHER.getHint())); ).addHint(GreatestAmongPermanentsValue.TOUGHNESS_OTHER_CONTROLLED_CREATURES.getHint()));
} }
private BighornerRancher(final BighornerRancher card) { private BighornerRancher(final BighornerRancher card) {

View file

@ -1,35 +1,30 @@
package mage.cards.b; package mage.cards.b;
import java.util.UUID;
import mage.abilities.common.SagaAbility; import mage.abilities.common.SagaAbility;
import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.keyword.DeathtouchAbility;
import mage.constants.*;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SagaChapter;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.filter.common.FilterBySubtypeCard; import mage.filter.common.FilterBySubtypeCard;
import mage.filter.common.FilterNonlandPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetNonlandPermanent;
import java.util.UUID;
/** /**
*
* @author weirddan455 * @author weirddan455
*/ */
public final class BindingTheOldGods extends CardImpl { public final class BindingTheOldGods extends CardImpl {
private static final FilterNonlandPermanent filter private static final FilterCard filter = new FilterBySubtypeCard(SubType.FOREST);
= new FilterNonlandPermanent("nonland permanent an opponent controls");
private static final FilterBySubtypeCard filter2
= new FilterBySubtypeCard(SubType.FOREST);
static {
filter.add(TargetController.OPPONENT.getControllerPredicate());
}
public BindingTheOldGods(UUID ownerId, CardSetInfo setInfo) { public BindingTheOldGods(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{G}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{G}");
@ -41,11 +36,11 @@ public final class BindingTheOldGods extends CardImpl {
// I Destroy target nonland permanent an opponent controls. // I Destroy target nonland permanent an opponent controls.
sagaAbility.addChapterEffect( sagaAbility.addChapterEffect(
this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I, this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I,
new DestroyTargetEffect(), new TargetNonlandPermanent(filter) new DestroyTargetEffect(), new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND)
); );
// II Search your library for a Forest card, put it onto the battlefield tapped, then shuffle your library. // II Search your library for a Forest card, put it onto the battlefield tapped, then shuffle your library.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II,
new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter2), true) new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)
); );
// III Creatures you control gain deathtouch until end of turn. // III Creatures you control gain deathtouch until end of turn.
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III,

View file

@ -10,9 +10,7 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterPermanent; import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.WasDealtDamageThisTurnPredicate;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.UUID; import java.util.UUID;
@ -22,14 +20,7 @@ import java.util.UUID;
*/ */
public final class BitterDownfall extends CardImpl { public final class BitterDownfall extends CardImpl {
private static final FilterPermanent filter private static final Condition condition = new SourceTargetsPermanentCondition(StaticFilters.FILTER_CREATURE_DAMAGED_THIS_TURN);
= new FilterCreaturePermanent("a creature that was dealt damage this turn");
static {
filter.add(WasDealtDamageThisTurnPredicate.instance);
}
private static final Condition condition = new SourceTargetsPermanentCondition(filter);
public BitterDownfall(UUID ownerId, CardSetInfo setInfo) { public BitterDownfall(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}");

View file

@ -0,0 +1,54 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.LandfallAbility;
import mage.abilities.common.TransformIntoSourceTriggeredAbility;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BlackChocobo extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent(SubType.BIRD, "Birds");
public BlackChocobo(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "");
this.subtype.add(SubType.BIRD);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
this.nightCard = true;
this.color.setGreen(true);
// When this permanent transforms into Black Chocobo, search your library for a land card, put it onto the battlefield tapped, then shuffle.
this.addAbility(new TransformIntoSourceTriggeredAbility(
new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_LAND_A), true)
));
// Landfall -- Whenever a land you control enters, Birds you control get +1/+0 until end of turn.
this.addAbility(new LandfallAbility(new BoostControlledEffect(
1, 0, Duration.EndOfTurn, filter, false
)));
}
private BlackChocobo(final BlackChocobo card) {
super(card);
}
@Override
public BlackChocobo copy() {
return new BlackChocobo(this);
}
}

View file

@ -0,0 +1,52 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.keyword.DeathtouchAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.filter.StaticFilters;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BlackWaltzNo3 extends CardImpl {
public BlackWaltzNo3(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}");
this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.WIZARD);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Deathtouch
this.addAbility(DeathtouchAbility.getInstance());
// Whenever you cast a noncreature spell, Black Waltz No. 3 deals 2 damage to each opponent.
this.addAbility(new SpellCastControllerTriggeredAbility(
new DamagePlayersEffect(2, TargetController.OPPONENT),
StaticFilters.FILTER_SPELL_A_NON_CREATURE, false
));
}
private BlackWaltzNo3(final BlackWaltzNo3 card) {
super(card);
}
@Override
public BlackWaltzNo3 copy() {
return new BlackWaltzNo3(this);
}
}

View file

@ -5,7 +5,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.decorator.ConditionalContinuousEffect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachTargetToTargetEffect;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect;
@ -16,19 +16,17 @@ import mage.abilities.keyword.HasteAbility;
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.common.FilterEquipmentPermanent;
import mage.filter.predicate.permanent.EquippedPredicate; import mage.filter.predicate.permanent.EquippedPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.SwordToken; import mage.game.permanent.token.SwordToken;
import mage.target.TargetPermanent; import mage.target.TargetPermanent;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.targetpointer.EachTargetPointer;
import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
@ -36,12 +34,10 @@ import java.util.UUID;
*/ */
public final class BlacksmithsTalent extends CardImpl { public final class BlacksmithsTalent extends CardImpl {
private static final FilterPermanent filter = new FilterEquipmentPermanent("equipment you control"); private static final FilterPermanent filter = new FilterCreaturePermanent();
private static final FilterPermanent filter2 = new FilterCreaturePermanent();
static { static {
filter.add(TargetController.YOU.getControllerPredicate()); filter.add(EquippedPredicate.instance);
filter2.add(EquippedPredicate.instance);
} }
public BlacksmithsTalent(UUID ownerId, CardSetInfo setInfo) { public BlacksmithsTalent(UUID ownerId, CardSetInfo setInfo) {
@ -59,10 +55,8 @@ public final class BlacksmithsTalent extends CardImpl {
this.addAbility(new ClassLevelAbility(2, "{2}{R}")); this.addAbility(new ClassLevelAbility(2, "{2}{R}"));
// At the beginning of combat on your turn, attach target Equipment you control to up to one target creature you control. // At the beginning of combat on your turn, attach target Equipment you control to up to one target creature you control.
Ability ability = new BeginningOfCombatTriggeredAbility( Ability ability = new BeginningOfCombatTriggeredAbility(new AttachTargetToTargetEffect());
new BlacksmithsTalentEffect() ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_EQUIPMENT));
);
ability.addTarget(new TargetPermanent(filter));
ability.addTarget(new TargetControlledCreaturePermanent(0, 1)); ability.addTarget(new TargetControlledCreaturePermanent(0, 1));
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 2))); this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 2)));
@ -71,10 +65,10 @@ public final class BlacksmithsTalent extends CardImpl {
// During your turn, equipped creatures you control have double strike and haste. // During your turn, equipped creatures you control have double strike and haste.
ability = new SimpleStaticAbility(new ConditionalContinuousEffect(new GainAbilityControlledEffect( ability = new SimpleStaticAbility(new ConditionalContinuousEffect(new GainAbilityControlledEffect(
DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter2 DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter
), MyTurnCondition.instance, "during your turn, equipped creatures you control have double strike")); ), MyTurnCondition.instance, "during your turn, equipped creatures you control have double strike"));
ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect( ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect(
HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter2 HasteAbility.getInstance(), Duration.WhileOnBattlefield, filter
), MyTurnCondition.instance, "and haste")); ), MyTurnCondition.instance, "and haste"));
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3))); this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3)));
} }
@ -88,32 +82,3 @@ public final class BlacksmithsTalent extends CardImpl {
return new BlacksmithsTalent(this); return new BlacksmithsTalent(this);
} }
} }
class BlacksmithsTalentEffect extends OneShotEffect {
BlacksmithsTalentEffect() {
super(Outcome.Benefit);
this.setTargetPointer(new EachTargetPointer());
staticText = "attach target Equipment you control to up to one target creature you control";
}
private BlacksmithsTalentEffect(final BlacksmithsTalentEffect effect) {
super(effect);
}
@Override
public BlacksmithsTalentEffect copy() {
return new BlacksmithsTalentEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
List<UUID> targets = this.getTargetPointer().getTargets(game, source);
if (targets.size() < 2) {
return false;
}
Permanent equipment = game.getPermanent(targets.get(0));
Permanent creature = game.getPermanent(targets.get(1));
return equipment != null && creature != null && creature.addAttachment(equipment.getId(), source, game);
}
}

View file

@ -0,0 +1,57 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BlazingBomb extends CardImpl {
public BlazingBomb(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}");
this.subtype.add(SubType.ELEMENTAL);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// Whenever you cast a noncreature spell, if at least four mana was spent to cast it, put a +1/+1 counter on this creature.
this.addAbility(new SpellCastControllerTriggeredAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance()),
StaticFilters.FILTER_NONCREATURE_SPELL_FOUR_MANA_SPENT, false
));
// Blow Up -- {T}, Sacrifice this creature: It deals damage equal to its power to target creature. Activate only as a sorcery.
Ability ability = new ActivateAsSorceryActivatedAbility(
new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE, "it"), new TapSourceCost()
);
ability.addCost(new SacrificeSourceCost());
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability.withFlavorWord("Blow Up"));
}
private BlazingBomb(final BlazingBomb card) {
super(card);
}
@Override
public BlazingBomb copy() {
return new BlazingBomb(this);
}
}

View file

@ -14,9 +14,11 @@ import mage.constants.Outcome;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard; import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -69,13 +71,14 @@ class BondOfRevivalEffect extends OneShotEffect {
if (player == null || card == null) { if (player == null || card == null) {
return false; return false;
} }
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.UntilYourNextTurn);
effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1));
if (player.moveCards(card, Zone.BATTLEFIELD, source, game)) { if (player.moveCards(card, Zone.BATTLEFIELD, source, game)) {
Permanent permanent = CardUtil.getPermanentFromCardPutToBattlefield(card, game);
if (permanent != null) {
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.UntilYourNextTurn);
effect.setTargetPointer(new FixedTarget(permanent.getId(), game));
game.addEffect(effect, source); game.addEffect(effect, source);
} }
}
return true; return true;
} }
} }

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